Skip to content

[iOS] Fix Switch custom colors on iOS 26#35385

Open
AdamEssenmacher wants to merge 224 commits into
dotnet:inflight/currentfrom
AdamEssenmacher:issue-35257
Open

[iOS] Fix Switch custom colors on iOS 26#35385
AdamEssenmacher wants to merge 224 commits into
dotnet:inflight/currentfrom
AdamEssenmacher:issue-35257

Conversation

@AdamEssenmacher
Copy link
Copy Markdown

@AdamEssenmacher AdamEssenmacher commented May 11, 2026

Description of Change

Fixes iOS 26 Switch custom color rendering by using UIKit's public classic sliding switch style when MAUI switch colors are customized.

On iOS 26, the automatic/liquid UISwitch style can ignore MAUI OffColor/OnColor track tinting and ThumbColor. This change opts switches with a non-null MAUI track or thumb color into UISwitchStyle.Sliding on iOS 26+, while leaving unstyled switches as UISwitchStyle.Automatic to preserve the native default appearance.

This also:

  • Reapplies colors after UIKit lifecycle/style changes that can rebuild or reset internal switch views.
  • Re-arms color reapply after window reattachment.
  • Clears native ThumbTintColor when ThumbColor is reset to null.
  • Keeps the existing Issue35257 Appium visual regression coverage from [iOS 26] Fix Switch ThumbColor and OffColor not applied on initial load #35400 and adds focused iOS handler tests for the style, reset, theme-change, and reattachment paths.

Conflict resolution:

Validation performed:

  • dotnet build src/Core/src/Core.csproj -f net10.0-ios26.0 -c Debug --no-restore
  • dotnet build src/Controls/tests/TestCases.iOS.Tests/Controls.TestCases.iOS.Tests.csproj -c Debug --no-restore

Issues Fixed

Fixes #35257

KarthikRajaKalaimani and others added 30 commits May 6, 2026 09:20
…net#34527)

<!-- Please let the below note in for people that find this PR -->
> [!NOTE]
> Are you waiting for the changes in this PR to be merged?
> It would be very helpful if you could [test the resulting
artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!

### Issue Details:

Horizontalspacing / Verticalspacing is not not applied to the first
column in GridItemLayout using CollectionView on Android platform.
        
### Root Cause:

The grid spacing was not being distributed symmetrically across the
active layout implementations, so edge items did not fully participate
when spacing changed at runtime.

### Description of Change:

- On Android, the fix in MauiRecyclerView.cs changes how RecyclerView
padding is handled for GridItemsLayout. Android was already using
SpacingItemDecoration, which applies half-spacing on all four sides of
each item. Previously, negative RecyclerView padding canceled that
spacing at the control edges. The branch keeps that negative-padding
behavior for non-grid layouts, but disables it for GridItemsLayout,
allowing the grid’s half-spacing to remain visible at the outer
perimeter. This makes the first row and first column visually respond
when spacing changes, but it also changes the grid behavior from spacing
only between items to spacing around the outside edges as well.

**Tested the behavior in the following platforms:**

- [x] Android
- [x] Windows
- [ ] iOS
- [ ] Mac

### Reference:

N/A

### Issues Fixed:

Fixes  dotnet#34257      

### Screenshots
| Before  | After  |
|---------|--------|
| <Video
src="https://github.com/user-attachments/assets/578dda69-1d60-474c-a6d8-23b3f9d29a50"
Width="300" Height="600"> | <Video
src="https://github.com/user-attachments/assets/7f3826e6-5922-4b6f-a6b9-de581b7db6c3"
Width="300" Height="600"> |
…otnet#32491)

<!-- Please let the below note in for people that find this PR -->
   > [!NOTE]
   > Are you waiting for the changes in this PR to be merged?
> It would be very helpful if you could [test the resulting
artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank
   you!

   ## Summary

Fixes a critical bug where `HybridWebView.InvokeJavaScriptAsync` would
timeout when passing JSON strings as parameters. The issue was caused by
improper JavaScript string escaping. The fix extracts business
logic into a new `HybridWebViewHelper` class and uses
`WebViewHelper.EscapeJsString` for proper escaping.

   **Fixes**: dotnet#32438

   ---

   ## The Problem

Issue dotnet#32438 reported that passing JSON strings to
`InvokeJavaScriptAsync` caused timeouts on Windows (required base64
encoding as workaround), while Android worked correctly.

   ### Root Cause

The old `EvaluateJavaScriptAsync` implementation used naive string
concatenation:

   ```csharp
   // OLD - Breaks when script contains quotes
   script = "try{eval('" + script + "')}catch(e){'null'};";
   ```

When the script contained JSON with quotes, this produced invalid
JavaScript:
   ```javascript
try{eval('window.HybridWebView.__InvokeJavaScript(1, 'method',
["{\"userId\":\"value\"}"])')}catch(e){'null'};
              ↑ BROKEN - conflicting quotes
   ```

Result: JavaScript execution failed, causing `InvokeJavaScriptAsync` to
timeout.

   ---

   ## The Solution

   ### Core Fix

   New implementation properly escapes JavaScript strings:

   ```csharp
   // NEW - Uses proper escaping
   var escapedScript = WebViewHelper.EscapeJsString(script);
   var wrappedScript = $$"""
   (function() {
       try {
let result = eval('{{escapedScript}}'); // ← Properly escaped
return JSON.stringify({ IsError: false, Result: JSON.stringify(result)
});
       } catch (error) {
           // ... error handling
       }
   })()
   """;
   ```

   ### Refactoring

Extracted ~360 lines from `HybridWebViewHandler` into new
`HybridWebViewHelper` class:
- `ProcessEvaluateJavaScriptAsync` - Script wrapping with proper
escaping
   - `ProcessInvokeJavaScriptAsync` - JavaScript call building
   - `ProcessInvokeDotNetAsync` - .NET method invocation from JS
   - `ProcessRawMessage` - Message routing

   Handler reduced from ~600 to 244 lines.

   ---

   ## Changes

   **New Files:**
   - ✨ `HybridWebViewHelper.cs` (470 lines) - Centralized business logic

   **Modified Files:**
   - 🔧 `HybridWebViewHandler.cs` - Delegates to helper
   - 🔧 `HybridWebViewHandler.Standard.cs` - Updated message processing
   - 🔧 `HybridWebViewHandler.Tizen.cs` - Updated message processing

   **Tests:**
   - 🔧 `HybridWebViewTestsBase.cs` - Added 15-second timeout
   - ✨ Added 5 new tests for JSON parameter scenarios
   - ✨ Added 3 enhanced quote-handling tests
   - 🔧 Renamed 20+ tests for clarity
   - 📝 Corrected error messages to use `InvokeJavaScriptAsync`

   **Test HTML:**
- Added `EchoJsonParameter`, `ParseAndStringifyJson`,
`ConcatenateJsonStrings`, `DecodeBase64AndEcho`, `CountJsonArrayItems`

   ---

   ## Testing

   **Platforms**: iOS, Android, Windows, MacCatalyst

   **Scenarios**:
   - JSON with quotes and special characters
   - Complex nested JSON
   - Multiple JSON parameters
   - Large arrays (100 items)
   - Base64 workaround (backward compatibility)

   ---

   ## Breaking Changes

   **None** - Fully backward compatible:
   - ✅ Existing code works unchanged
   - ✅ Base64 workaround still works
   - ✅ No public API changes

   ### Migration Note

   If using base64 workaround, you can now simplify:

   ```csharp
   // OLD workaround (still works):
   var json = JsonSerializer.Serialize(obj);
   var base64 = Convert.ToBase64String(Encoding.UTF8.GetBytes(json));
await hybridWebView.InvokeJavaScriptAsync<string>("fn", ..., [base64],
...);

   // NEW (now works directly):
   var json = JsonSerializer.Serialize(obj);
await hybridWebView.InvokeJavaScriptAsync<string>("fn", ..., [json],
...);
   ```

   ---

   ## Checklist

   - [x] Code follows .NET MAUI conventions
   - [x] 8 new/enhanced tests added
   - [x] 20+ tests renamed for clarity
   - [x] No breaking changes
   - [x] Backward compatible
   - [x] Error messages corrected
   - [x] Formatted with `dotnet format`
   - [x] XML documentation complete
   - [x] Tested on multiple platforms

---------

Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com>
Co-authored-by: mattleibow <1096616+mattleibow@users.noreply.github.com>
… on collection update (dotnet#31275)

<!-- Please let the below note in for people that find this PR -->
   > [!NOTE]
   > Are you waiting for the changes in this PR to be merged?
> It would be very helpful if you could [test the resulting
artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
  Thank you!

### Root Cause
**Windows**
Position not updating on item add: CarouselView stayed at the old
position after an item was added, leaving current/previous positions
unsynced.

Cascading events: With `ItemsUpdatingScrollMode.KeepItemsInView`,
programmatic smooth scrolls triggered multiple ViewChanged calls,
causing `PositionChanged` to fire repeatedly with intermediate values.

**Android**
Programmatic smooth scrolls produced the same cascading
`PositionChanged` events as on Windows.

### Description of Change

**Windows**
Position Update: On item add, `ItemsView.Position` is explicitly set
based on `ItemsUpdatingScrollMode`, keeping current and previous
positions in sync.

Prevent Cascading Events: Added `_isInternalPositionUpdate`. For
collection changes, animations are disabled (animate = false) so
scrolling jumps directly, firing `PositionChanged` only once.

**Android**
Reused the `_isInternalPositionUpdate` logic. Disabled animations during
collection changes, ensuring a single clean position update without
duplicate events.

### Issues Fixed
Fixes dotnet#29529

Tested the behaviour in the following platforms
- [x] Android
- [x] Windows
- [x] iOS
- [x] Mac

### Screenshots
| Before Issue Fix | After Issue Fix |
|------------------|-----------------|
| <img width="350" alt="withoutfix"
src="https://github.com/user-attachments/assets/d66f0352-a91f-4b85-bb9f-e0e54e55aa5f"
/> | <img width="350" alt="withfix"
src="https://github.com/user-attachments/assets/d120f268-6954-498d-aab0-42bc3745e296"
/> |

---------
…3953)

<!-- Please let the below note in for people that find this PR -->
> [!NOTE]
> Are you waiting for the changes in this PR to be merged?
> It would be very helpful if you could [test the resulting
artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!


<!--
!!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING
MAIN. !!!!!!!
-->

### Issue Details
On iOS/MacCatalyst 26+, when the app theme changes (light ↔ dark), the
Switch's ThumbColor resets to white instead of keeping the custom color.

### Root Cause
On iOS/MacCatalyst 26+, UIKit resets the UISwitch's ThumbTintColor to
its default value during theme transitions.

### Description of Change
Register for trait collection changes to detect theme switches, then
re-apply the custom ThumbColor after UIKit completes its styling.

Validated the behavior in the following platforms
 
- [x] Android
- [x] Windows
- [x] iOS
- [x] Mac
 
### Issues Fixed
  
Fixes dotnet#33783 
Fixes dotnet#33767 

### Output  ScreenShot

|Before|After|
|--|--|
| <video
src="https://github.com/user-attachments/assets/aa595096-4f75-4d7e-b31e-f1f0acc24208"
>| <video
src="https://github.com/user-attachments/assets/386f9e6e-6df7-40c5-a3e7-59a0bde31df6">|
<!-- Please let the below note in for people that find this PR -->
> [!NOTE]
> Are you waiting for the changes in this PR to be merged?
> It would be very helpful if you could [test the resulting
artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!

<!--
!!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING
MAIN. !!!!!!!
-->

### Issue Details
WebView does not scroll when placed inside a ScrollView. The parent
ScrollView intercepts vertical touch gestures, preventing the WebView
from scrolling its internal content.

### Root Cause
The parent ScrollView was intercepting all touch events without checking
if the child WebView needed to scroll. This prevented the WebView from
receiving touch events and handling its own scrolling.

### Description of Change
Added touch event handling to prevent parent interception when WebView
scrolls. When touch begins or continues, WebView requests exclusive
control from parent. When touch ends or cancels, control returns to
parent. This enables WebView scrolling while preserving normal
parent-child interaction.
 
Validated the behavior in the following platforms
 
- [x] Android
- [x] Windows
- [x] iOS
- [x] Mac
 
### Issues Fixed
  
Fixes dotnet#32971  

### Output  ScreenShot

|Before|After|
|--|--|
| <video
src="https://github.com/user-attachments/assets/12b3ca6e-582d-4a50-9ea1-f49027f2d907"
>| <video
src="https://github.com/user-attachments/assets/2fbfd03c-4432-49e9-8b2d-6e7643f57487">|
… to null (dotnet#34741)

<!-- Please let the below note in for people that find this PR -->
> [!NOTE]
> Are you waiting for the changes in this PR to be merged?
> It would be very helpful if you could [test the resulting
artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!

<!--
!!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING
MAIN. !!!!!!!
-->

### Issue Details
On iOS and macCatalyst, setting BackgroundColor = null on an Entry or
Editor at runtime does not restore the native default appearance. Once a
custom color is applied, it persists even after the property is cleared.

### Root Cause
The shared iOS background update logic only resets BackgroundColor =
null for LayoutView subclasses — all other UIView types silently return.
MauiTextField (Entry) and MauiTextView (Editor) are not LayoutView
subclasses, so the reset is skipped.

### Description of Change
Platform-specific MapBackground methods were added to
EntryHandler.iOS.cs and EditorHandler.iOS.cs. When
Background.IsNullOrEmpty(), they now explicitly set
platformView.BackgroundColor = null to restore native appearance.
 
Validated the behavior in the following platforms
 
- [x] Android
- [x] Windows
- [x] iOS
- [x] Mac
 
### Issues Fixed
  
Fixes dotnet#34611   

### Output  ScreenShot

|Before|After|
|--|--|
| <video
src="https://github.com/user-attachments/assets/5ca30c6d-c069-4c04-989b-4dae36584cb4"
>| <video
src="https://github.com/user-attachments/assets/ee9e2a2e-c210-47cc-9f85-2526780d398b">|

---------

Co-authored-by: Jakub Florkowski <42434498+kubaflo@users.noreply.github.com>
…t placed child to the Border control in iOS/ Mac platform (dotnet#33330)

<!-- Please let the below note in for people that find this PR -->
> [!NOTE]
> Are you waiting for the changes in this PR to be merged?
> It would be very helpful if you could [test the resulting
artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!

### Issue Details:

Stacklayout is not rendered when clip is applied and StackLayout placed
child to the Border control in iOS/ Mac platform.
       
### Root Cause:

When the clip is applied to the StackLayout, its ContainerView
(WrapperView) is being inserted at index 0 to its parent control Border,
which places it below Border's background layer in the z-order. As a
result, the stacklayout is not visible in the view in iOS and Mac
platform.

### Description of Change:

The wrapper view is brought to the front of its parent’s subview stack
so it renders above the Border background in the Z order.

**Tested the behavior in the following platforms.**

- [x] Android
- [x] Windows
- [x] iOS
- [x] Mac

### Reference:

N/A

### Issues Fixed:

Fixes  dotnet#33241    

### Screenshots
| Before  | After  |
|---------|--------|
| <img width="369" height="606" alt="image"
src="https://github.com/user-attachments/assets/0be8bc27-5de4-41ad-a41f-92581513ac55"
/> | <img width="369" height="606" alt="image"
src="https://github.com/user-attachments/assets/6a3590f6-2763-473a-aa91-ee1113e48ec3"
/> |
…cription is set (dotnet#33979)

<!-- Please let the below note in for people that find this PR -->
> [!NOTE]
> Are you waiting for the changes in this PR to be merged?
> It would be very helpful if you could [test the resulting
artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!

### Root Cause
WinUI `TextBlock` (used by `Label`) automatically exposes its `Text` to
UI Automation. `ContentPanel` uses the default
`FrameworkElementAutomationPeer`, which exposes both the parent’s
`AutomationProperties.Name` and all child elements. Unlike Android
(`NoHideDescendants`) or iOS (`AccessibilityElementsHidden`), Windows
has no single property to hide descendants while keeping the parent
accessible. As a result, both Tab navigation and Browse mode announced
duplicate content.

### Description of Change
Implemented a custom `ContentPanelAutomationPeer` that overrides three
core UI Automation methods (GetAutomationControlTypeCore ,
`GetLocalizedControlTypeCore`, `GetChildrenCore`) to conditionally
modify behavior when Description is present.
 
When a Description exists, the control is exposed as
`AutomationControlType.Text` (enables browse mode navigation;
alternatives like Custom announce "custom" suffix, Group causes browse
mode to skip the element), the "text" announcement suffix is suppressed
via empty `GetLocalizedControlTypeCore()` return, and child elements are
hidden by returning null from `GetChildrenCore()` to prevent
duplication. When no Description is present, default behavior is
preserved with `AutomationControlType.Custom` and children remain
accessible.
The `HasDescription` helper property centralizes the non-empty
Description check across all three override methods, ensuring consistent
conditional logic following the MAUI `AutomationPeer patterns.

### Issues Fixed

Fixes dotnet#33373 

### Platforms Tested

- [ ] iOS
- [x] Android  
- [x] Windows
- [x] Mac

### Screenshots
| Before Fix | After Fix |
|------------|-----------|
| <video width="350" alt="withoutfix"
src="https://github.com/user-attachments/assets/c5f8f114-fbeb-42d1-8601-e75dad57a1a7"
/> | <video width="350" alt="withfix"
src="https://github.com/user-attachments/assets/d2405df5-64f3-4cd9-8b38-40911ce4fbd6"
/> |

---------
…#33459)

<!--
!!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING
MAIN. !!!!!!!
-->

### Description of Change

<img width="870" height="73" alt="image"
src="https://github.com/user-attachments/assets/e27cb50d-db93-431b-831c-24f7faedf9b1"
/>

Invalidation propagation can be quite consuming especially when
switching binding context and multiple properties change (i.e. having
multiple labels inside a layout, all of them changing the text).

Ti PR avoids useless propagations (same behavior `requestLayout`
intrinsically has on Android).
# Conflicts:
#	src/Core/src/Platform/iOS/MauiView.cs
…g lifecycle transition (dotnet#34901)

<!-- Please let the below note in for people that find this PR -->
> [!NOTE]
> Are you waiting for the changes in this PR to be merged?
> It would be very helpful if you could [test the resulting
artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!

## Description

Fixes dotnet#34900

Replaces an opaque `NullReferenceException` with an informative
`InvalidOperationException` in `ContainerView` when the Android
`Context` is no longer available.

### Root Cause

`IMauiContext.Context` (the Android `Context`) is stored via a
`WeakReference` to the Activity. During lifecycle transitions (e.g.,
Activity being GC'd), this reference can become null. When
`NavigationRootManager.Connect()` calls
`view.ToContainerView(mauiContext)`, it creates a `ContainerView` whose
base `LinearLayout` constructor receives a null `Context`, causing an
NRE deep in the JNI interop layer.

### Fix

Added a null-coalescing throw expression in the `ContainerView`
constructor:

```csharp
public ContainerView(IMauiContext context)
    : base(context.Context ?? throw new InvalidOperationException(
        "Unable to create a ContainerView: the Android Context is no longer available. " +
        "This can occur when the Activity has been collected during a lifecycle transition."))
```

This provides a clear, actionable error message instead of the cryptic
NRE that pointed at `NavigationRootManager.Connect` line 60.

### Stack trace from the original crash

```
[AndroidRuntime] at Microsoft.Maui.Platform.NavigationRootManager.Connect
[AndroidRuntime] at Microsoft.Maui.Handlers.WindowHandler.CreateRootViewFromContent
[AndroidRuntime] at Microsoft.Maui.Handlers.WindowHandler.MapContent
```

---------
<!-- Please let the below note in for people that find this PR -->
> [!NOTE]
> Are you waiting for the changes in this PR to be merged?
> It would be very helpful if you could [test the resulting
artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!

<!--
!!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING
MAIN. !!!!!!!
-->
### Root Cause: 
CarouselView does not utilize the ItemSpacing property during layout
styling
### Description of Change
Added an item container style based on the horizontal or vertical
orientation of the CarouselView, and applied a corresponding style to
the ListViewBase
<!-- Enter description of the fix in this section -->

### Issues Fixed

<!-- Please make sure that there is a bug logged for the issue being
fixed. The bug should describe the problem and how to reproduce it. -->

Fixes dotnet#29772 
### Tested the behaviour in the following platforms

- [x] Windows
- [x] Android
- [x] iOS
- [x] Mac

### Screenshot

| Before Issue Fix | After Issue Fix |
|----------|----------|
| <img
src="https://github.com/user-attachments/assets/4af88696-c5a6-4683-bceb-1933781368f3">
| <img
src="https://github.com/user-attachments/assets/f55c2b45-22c3-4ab2-aae5-c668adaf33e4">
|
<!--
Are you targeting main? All PRs should target the main branch unless
otherwise noted.
-->
<!-- Please let the below note in for people that find this PR -->
> [!NOTE]
> Are you waiting for the changes in this PR to be merged?
> It would be very helpful if you could [test the resulting
artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!
<!--
!!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING
MAIN. !!!!!!!
-->

### Issue Detail

RadioButtonGroup is not functioning correctly when RadioButton controls
are placed inside a ContentView in .NET 10.

### Root Cause

PR [dotnet#32640](dotnet#32604) (fixing dotnet#32466)
removed the coerceValue and Value = this initialiser from RadioButton,
so RadioButton.Value now defaults to null. This introduced two distinct
bugs in the ContentView+ControlTemplate scenario:

- AddRadioButton guard object.Equals(radioButton.Value,
this.SelectedValue) evaluates to object.Equals(null, null) == true,
causing every button added via a ControlTemplate to be auto-checked
immediately.
- When the layout is not yet attached to a Page (common during
ControlTemplate inflation), GetVisualRoot() returns null. The original
fallback radioButton.Parent resolves to the button's immediate parent
(Border), which only contains that single button — so other RadioButtons
in the same group are never found and never unchecked.

### Description of Change

- Updated the equality check in AddRadioButton to ensure comparison only
happens when SelectedValue is not null. This avoids unintended selection
during initialization.
- Improved the root resolution logic in UncheckOtherRadioButtonsInScope.
If no visual root is found, fallback to the RadioButtonGroupController’s
layout (group container).

### Issue Fixed
Fixes dotnet#34759 

### Screenshots

| Before  | After |
|---------|--------|
|  <video
src="https://github.com/user-attachments/assets/39e1e45d-dd04-42b7-bb85-aea83124b0cb">
|   <video
src="https://github.com/user-attachments/assets/252587b2-10ae-4eb9-968e-de114756aa0c"> 
|

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
### Description of Change

I tried to improve the access time on BindableProperty given they're
being accessed a lot during the application usage.
As we can see using Array + SIMD is actually performing better when the
number of BindableProperty set on a BindableObject is less than ~10,
though it becomes worse after that.
Array + SIMD also (obviously) allocates less memory.

Considering that SIMD may perform differently on different platforms I
felt it would be better to simply improve the current Dictionary based
implementation by leveraging:
- integer keys
- CollectionMarshal on GetOrAdd

### Benchmarks

| Strategy | PropertiesToSet | Mean | Error | StdDev | Gen0 | Gen1 |
Allocated |
|----------------------- |----------------
|------------:|---------:|---------:|-------:|-------:|----------:|
| Dictionary<BindableProperty> | 1 | 65.22 ns | 0.246 ns | 0.218 ns |
0.0889 | 0.0001 | 744 B |
| Array + SIMD | 1 | **52.93 ns** | 0.750 ns | 0.665 ns | 0.0678 | - |
568 B |
| Dictionary<int,> | 1 | 59.29 ns | 0.563 ns | 0.440 ns | 0.0889 |
0.0001 | 744 B |
| |
| Dictionary<BindableProperty> | 3 | 150.70 ns | 0.344 ns | 0.322 ns |
0.1500 | 0.0005 | 1256 B |
| Array + SIMD | 3 | **122.09 ns** | 0.163 ns | 0.127 ns | 0.1290 |
0.0002 | 1080 B |
| Dictionary<int,> | 3 | 133.50 ns | 0.231 ns | 0.180 ns | 0.1500 |
0.0005 | 1256 B |
| |
| Dictionary<BindableProperty> | 8 | 426.76 ns | 0.686 ns | 0.641 ns |
0.3662 | 0.0033 | 3064 B |
| Array + SIMD | 8 | **318.26 ns** | 0.438 ns | 0.409 ns | 0.3028 |
0.0024 | 2536 B |
| Dictionary<int,> | 8 | 338.98 ns | 2.381 ns | 1.988 ns | 0.3662 |
0.0038 | 3064 B |
| |
| Dictionary<BindableProperty> | 15 | 773.49 ns | 2.593 ns | 2.298 ns |
0.5798 | 0.0095 | 4856 B |
| Array + SIMD | 15 | 665.18 ns | 0.846 ns | 0.791 ns | 0.5531 | 0.0076
| 4632 B |
| Dictionary<int,> | 15 | **581.04 ns** | 0.431 ns | 0.360 ns | 0.5798 |
0.0095 | 4856 B |
| |
| Dictionary<BindableProperty> | 30 | 1,542.20 ns | 3.342 ns | 3.126 ns
| 1.1692 | 0.0381 | 9784 B |
| Array + SIMD | 30 | 1,419.83 ns | 1.301 ns | 1.154 ns | 1.0796 |
0.0324 | 9032 B |
| Dictionary<int,> | 30 | **1,165.86 ns** | 1.599 ns | 1.496 ns | 1.1692
| 0.0381 | 9784 B |
| |
| Dictionary<BindableProperty> | 50 | 2,648.69 ns | 3.759 ns | 2.935 ns
| 2.0828 | 0.1183 | 17448 B |
| Array + SIMD | 50 | 2,587.54 ns | 3.111 ns | 2.429 ns | 1.8196 |
0.0916 | 15224 B |
| Dictionary<int,> | 50 | **1,997.17 ns** | 2.174 ns | 2.033 ns | 2.0828
| 0.1183 | 17448 B |
…otnet#33891)

<!-- Please let the below note in for people that find this PR -->
> [!NOTE]
> Are you waiting for the changes in this PR to be merged?
> It would be very helpful if you could [test the resulting
artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!

### Root Cause
When a password `Entry` field is empty and text is programmatically set,
the obfuscation logic calculates a negative insert position `(0 - 4 =
-4)`. String. `Insert()` doesn't accept negative indices, causing an
`ArgumentOutOfRangeException` crash in windows.

### Description of Change

Added `Math.Max(0, ...)` to clamp the insert position to a minimum of 0,
preventing negative **values.**

### Issues Fixed

Fixes dotnet#33334 

### Platforms Tested

- [x] iOS
- [x] Android  
- [x] Windows
- [x] Mac

### Screenshots
| Before Fix | After Fix |
|------------|-----------|
| <img width="350" alt="withoutfix"
src="https://github.com/user-attachments/assets/43993544-7700-4bff-a04c-d34b999ac962"
/> | <img width="350" alt="withfix"
src="https://github.com/user-attachments/assets/e7f12e20-1cd8-45e7-b07d-bfe7ebac6248"
/> |
…eaving custom state (dotnet#33346)

<!-- Please let the below note in for people that find this PR -->
> [!NOTE]
> Are you waiting for the changes in this PR to be merged?
> It would be very helpful if you could [test the resulting
artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!

<!--
!!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING
MAIN. !!!!!!!
-->

### Root Cause:

The issue occurs because platform handlers on iOS and Android do not
restore button properties to their default values when VisualState
setters are removed. In .NET MAUI, when a control transitions from a
custom VisualState back to the normal state, property values are set to
null to indicate that defaults should be restored. However, the platform
handlers interpreted these null values as instructions to take no
action, causing previously applied custom values to remain.

### Fix Description:

The fix involves updating button-specific extension methods to ensure
properties are correctly restored to their platform default values when
property values are null.

For iOS, the UpdateBackground extension method now explicitly handles
UIButton instances. When the background is null or empty,
RemoveBackgroundLayer() is called first to prevent stacking gradient
layers, and the background is reset to UIColor.Clear. This restores
proper transparency and aligns with the default iOS button appearance.
When a valid background is provided, the update continues through the
existing view background logic.

The UpdateTextColor method was also improved to restore the system
default text color correctly when TextColor is null. If the button is
attached to a UIWindow, the handler clears explicit overrides using
SetTitleColor(null, …) for the Normal, Highlighted, and Disabled states,
allowing iOS to fall back to its natural appearance-proxy behavior. It
then restores TintColor using window.TintColor rather than a hardcoded
system color. This ensures the app’s global tint is respected and
prevents clearing appearance settings during the initial render phase.

For Android, a MaterialButton-specific UpdateTextColor overload was
introduced in MauiMaterialButton. The default Material theme
ColorStateList is cached as DefaultTextColors during construction,
before any MAUI property mapping occurs. When TextColor becomes null,
the cached state list is restored directly, preserving all theme-defined
states (normal, disabled, etc.). This approach avoids creating temporary
controls and guarantees consistent restoration of the original Material
theme colors.

### Issues Fixed
Fixes dotnet#19690

### Tested the behaviour in the following platforms
- [x] Mac
- [x] Windows
- [x] iOS
- [x] Android

### Output Screenshot
| Platform | Before Fix | After Fix |
|----------|----------|----------|
| Android | <video
src="https://github.com/user-attachments/assets/d96c404f-99b0-4dd2-bfdf-01adf629466e">
| <video
src="https://github.com/user-attachments/assets/e85313ef-3155-42d3-a1bc-c1b6ef8587b0">
|
|iOS | <video
src="https://github.com/user-attachments/assets/416724b2-f8fe-4146-be56-12c3f6808693">
| <video
src="https://github.com/user-attachments/assets/962ce83a-de7f-4ca0-8339-c8ce88e99288">
|
…tating the device with dialog open (dotnet#31910)

<!-- Please let the below note in for people that find this PR -->
> [!NOTE]
> Are you waiting for the changes in this PR to be merged?
> It would be very helpful if you could [test the resulting
artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!

<!--
!!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING
MAIN. !!!!!!!
-->
### Issue Details

- On Android, when opening a TimePicker and then rotating the device,
the picker dialog doesn’t seem to redraw itself to match the new screen
dimensions.

### Root Cause of the issue

- TimePicker lacks orientation change detection entirely. When the
device rotates while the dialog is open, TimePicker has no mechanism to
dismiss and re-show the dialog with updated layout, unlike DatePicker
which detects orientation changes and refreshes the dialog display.

### Description of Change

- Added ConnectHandler and related event subscriptions
(ViewAttachedToWindow/ViewDetachedFromWindow) to manage display info
changes and cleanup when the view is attached or detached. This helps
ensure the time picker dialog responds to device orientation changes and
releases resources properly.

- Implemented OnMainDisplayInfoChanged to dismiss and recreate the time
picker dialog with the current time when the device orientation changes,
preserving user selection progress.

### Issues Fixed

<!-- Please make sure that there is a bug logged for the issue being
fixed. The bug should describe the problem and how to reproduce it. -->

Fixes dotnet#31658 

### Reference

-
[DatePickerHandler](https://github.com/dotnet/maui/blob/main/src/Core/src/Handlers/DatePicker/DatePickerHandler.Android.cs)

### Tested the behaviour in the following platforms

- [ ] - Windows
- [x] - Android
- [ ] - Mac
- [x] - iOS

### Output
| Before | After |
|----------|----------|
| <video
src="https://github.com/user-attachments/assets/55091d04-0aa3-4794-aab8-fb5dfc71624e">
| <video
src="https://github.com/user-attachments/assets/56c01750-00a1-4c63-8788-ca6c35d7dbd5">
|

<!--
Are you targeting main? All PRs should target the main branch unless
otherwise noted.
-->
<!-- Please let the below note in for people that find this PR -->
> [!NOTE]
> Are you waiting for the changes in this PR to be merged?
> It would be very helpful if you could [test the resulting
artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!

### Issue details

Changing the Location property on a Microsoft.Maui.Controls.Maps.Pin
does not change the location of the Pin on the map.

### Root cause
`MapPinHandler.Android.cs` was only updating the `MarkerOptions` object
when the Location property changed
The actual `Marker` object already added to the map was not being
updated, causing the pin to stay in its original location

<!--
!!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING
MAIN. !!!!!!!
-->

### Description of change

* **Improved marker association logic**: Updated the `AddPins` method in
`MapHandler` to store marker references in the `MapPinHandler` for
future property updates, ensuring tighter integration between pins and
markers.

Validated the behaviour in the following platforms

- [x] Android
- [ ] Windows
- [ ] iOS
- [ ] Mac

### Issues Fixed

<!-- Please make sure that there is a bug logged for the issue being
fixed. The bug should describe the problem and how to reproduce it. -->

Fixes dotnet#12916

<!--
Are you targeting main? All PRs should target the main branch unless
otherwise noted.
-->

### Output
| Before| After|
|--|--|
| <video
src="https://github.com/user-attachments/assets/83bedab7-846b-41c1-872e-b6e0d0cd81a4">
| <video
src="https://github.com/user-attachments/assets/fd86fd38-6619-4cf9-999a-de9d05b21e17">
|

---------

Co-authored-by: Jakub Florkowski <42434498+kubaflo@users.noreply.github.com>
…#34687)

## Description

Fixes dotnet#19866

On iOS, tapping the status bar should scroll the topmost `UIScrollView`
to the top. This was not working for `CollectionView`, particularly when
hosted inside a `Shell`.

## Root Cause

iOS disables its scroll-to-top behavior when **multiple** `UIScrollView`
instances in the view hierarchy have `scrollsToTop = true`. The Shell
flyout's internal `AccessibilityNeutralTableView` (a `UITableView`
subclass) defaults `scrollsToTop` to `true`, conflicting with the
`CollectionView`'s `UICollectionView`.

## Fix

Two changes:

1. **`ItemsViewController2.ViewDidLoad()`** — Explicitly set
`CollectionView.ScrollsToTop = true` to opt in to scroll-to-top behavior
2. **`ShellTableViewController.AccessibilityNeutralTableView`** — Set
`ScrollsToTop = false` on Shell's flyout table view to prevent the
multi-scroll-view conflict

This follows the existing codebase pattern where `ScrollsToTop` is
explicitly managed (e.g., `ShellSectionRootHeader` and
`ContextActionCell` both set it to `false`).

## Validation

Verified with a Sandbox app using a grouped `CollectionView` inside a
`Shell` with `TabBar`:
- **Without fix**: Diagnostic dump shows two scroll views with
`scrollsToTop=True` → status bar tap does nothing
- **With fix**: Only `MauiCollectionView` has `scrollsToTop=True` →
status bar tap scrolls to top correctly

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ht space above the tab bar even if the page title is empty (dotnet#30382)

<!-- Please let the below note in for people that find this PR -->
> [!NOTE]
> Are you waiting for the changes in this PR to be merged?
> It would be very helpful if you could [test the resulting
artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!

### Root cause

The root cause of this issue is that the Windows Shell implementation
always reserves space for the header area when
Shell.FlyoutBehavior="Flyout" is set, even when pages have no title or
header content. The NavigationView control allocates a fixed height
above the tab bar for the header region without checking if the current
page actually needs this space. This results in an unwanted gap between
the flyout toggle area and the tab bar when navigating to pages with
empty titles, creating visual inconsistency compared to
FlyoutBehavior="Disabled" mode where no header space is reserved for
empty content.

### Description of Issue Fix

The fix involves adding header visibility logic to the Windows Shell
implementation to remove unwanted space above the tab bar in flyout
mode. The implementation introduces three key methods in
RootNavigationView.cs: UpdateHeaderVisibility() to evaluate header
visibility requirements, IsHeaderContentEmpty() to determine if the
toolbar contains no title or title view when in flyout mode, and
CollapseEmptyHeader() to properly hide empty headers. The toolbar
property setter now calls UpdateHeaderVisibility() when the toolbar
changes, and ShellView.cs triggers header visibility updates during tab
navigation. This ensures empty headers don't occupy unnecessary space in
flyout mode while maintaining normal behavior when headers contain
content, resolving the spacing issue above the tab bar when switching
between pages in Shell applications on Windows.

Tested the behavior in the following platforms.
 
- [x] Windows
- [x] Mac
- [x] iOS
- [x] Android

### Issues Fixed

<!-- Please make sure that there is a bug logged for the issue being
fixed. The bug should describe the problem and how to reproduce it. -->

Fixes dotnet#30254

<!--
Are you targeting main? All PRs should target the main branch unless
otherwise noted.
-->

### Resaved Test snapshots

Resaved the below test snapshot because the ContentPage did not have a
title. So, resaved the test snapshot based on the fix.

1. ShellFlowDirectionUpdate
2. VerifyFlyoutBackgroundColor
3. VerifyHamburgerIcon
4. Issue23834FlyoutMisbehavior

### Output

| Before Issue Fix | After Issue Fix |
|----------|----------|
| <video width="270" height="600"
src="https://github.com/user-attachments/assets/ca496f96-2500-429f-8720-a8adb7925fce">
| <video width="270" height="600"
src="https://github.com/user-attachments/assets/1f0dedd2-4808-46bf-98cf-843ca70294ef">
|
…forms (dotnet#30369)

<!-- Please let the below note in for people that find this PR -->
> [!NOTE]
> Are you waiting for the changes in this PR to be merged?
> It would be very helpful if you could [test the resulting
artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!

<!--
!!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING
MAIN. !!!!!!!
-->

### Issue Detail
The FlowDirection property is not respected by the TimePicker control.
Setting FlowDirection="RightToLeft" has no visual effect on the control
across any platform.

### Root Cause
iOS: The MapFlowDirection method incorrectly used the concrete
TimePickerHandler type instead of the ITimePickerHandler interface. This
bypassed platform-specific logic and defaulted to
ViewHandler.MapFlowDirection, which lacked the necessary text alignment
handling.

Android: The FlowDirection property was not correctly mapped to the
native implementation, resulting in incorrect or missing text alignment
updates.

### Description of Change
iOS: Updated the MapFlowDirection method to use the correct handler
interface. Implemented alignment logic in TimePickerExtensions.cs using
EffectiveFlowDirection to properly align text for both RTL and LTR
layouts.

Android: Implemented a platform-specific MapFlowDirection method in
TimePickerHandler.Android.cs:
For 12-hour format, UpdateTextAlignment is applied to align localized
"AM/PM" text based on FlowDirection.
For 24-hour format, alignment is handled via UpdateFlowDirection, which
sets LayoutDirection and TextDirection to align purely numeric content

Validated the behaviour in the following platforms
- [x] Android
- [ ] Windows dotnet#30322 
- [x] iOS
- [ ] Mac dotnet#30322 

### Issues Fixed:
Fixes dotnet#30192 

### Screenshots

| Before  | After |
|---------|--------|
|  <img
src="https://github.com/user-attachments/assets/37c4a442-1011-4c23-bc2e-8743bc11adc7">
|  <img
src="https://github.com/user-attachments/assets/91b69287-b8eb-4d40-b8a1-0734ef1f89db"> 
|
…dotnet#26217)

### Root cause

The issue arises because the OnNavigationViewSizeChanged method fails to
properly reset the layout measurements before arranging the
NavigationView. As a result, the NavigationView does not correctly
update its layout in response to size changes, causing misalignment or
rendering issues in the ScrollView.

### Description of Issue Fix

The fix involves updating the OnNavigationViewSizeChanged() method to
include a call to InvalidateMeasure() before arranging the
NavigationView. This ensures that the layout is accurately recalculated,
allowing the ScrollView and other elements within the TabbedPage to be
properly measured and arranged during the subsequent layout cycle. This
effectively resolves alignment and rendering issues.

Additionally, the Arrange() call is retained within the SizeChanged
handler to prevent test failures, specifically avoiding timeout issues
observed in the ChangingToNewMauiContextDoesntCrash test. This
combination ensures stable layout behavior while resolving the clipping
and scrolling issues that occur after window resizing.

### Why Tests were not added:

**Regarding the test case:** The issue only occurs when resizing the
window, so it is not possible to add a test case for the window resizing
behavior.

Tested the behavior in the following platforms.
 
- [x] Windows
- [x] Mac
- [x] iOS
- [x] Android

### Issues Fixed

<!-- Please make sure that there is a bug logged for the issue being
fixed. The bug should describe the problem and how to reproduce it. -->

Fixes dotnet#26103
Fixes dotnet#11402
Fixes dotnet#20028

<!--
Are you targeting main? All PRs should target the main branch unless
otherwise noted.
-->

### Resaved Test snapshots
 
Resaved the below-mentioned test snapshot because elements in the
TabbedPage were not properly aligned before the fix. The layout changes
in OnNavigationViewSizeChanged (adding Arrange() after
InvalidateMeasure()) now ensure proper element alignment within the
TabbedPage.
 
1. DefaultSelectedTabTextColorShouldApplyProperly
2. FontImageSourceColorShouldApplyOnTabIcon
3. VerifyTabbedPageMenuItemTextColor
4. DynamicFontImageSourceColorShouldApplyOnTabIcon
5. Issue1323Test
6. TabBarIconsShouldAutoscaleTabbedPage

### Output

| Before Issue Fix | After Issue Fix |
|----------|----------|
| <video width="270" height="600"
src="https://github.com/user-attachments/assets/67d34cc9-323c-4a8d-afc6-dbcceb558ee5">
| <video width="270" height="600"
src="https://github.com/user-attachments/assets/0ddb5ce5-300a-4088-98f4-f9a3981d6951">
|

---------
<!-- Please let the below note in for people that find this PR -->
> [!NOTE]
> Are you waiting for the changes in this PR to be merged?
> It would be very helpful if you could [test the resulting
artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!

<!--
!!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING
MAIN. !!!!!!!
-->

### Root Cause:
The UpdateCharacterSpacing method didn't apply spacing to the individual
text blocks in TimePicker

### Description of Change 
Enhanced the UpdateCharacterSpacing method to apply CharacterSpacing to
individual text blocks (HourTextBlock, MinuteTextBlock, and
PeriodTextBlock) within the TimePicker. This ensures the property works
correctly even when the control is loaded asynchronously.

<!-- Enter description of the fix in this section -->

### Issues Fixed

<!-- Please make sure that there is a bug logged for the issue being
fixed. The bug should describe the problem and how to reproduce it. -->

Fixes dotnet#30199 

### Tested the behaviour in the following platforms

- [x] Windows
- [x] Android
- [x] iOS
- [ ] Mac

### Screenshot

| Before Issue Fix | After Issue Fix |
|----------|----------|
| <img width="1217" height="915" alt="beforeFix30199"
src="https://github.com/user-attachments/assets/0b955bf7-5e75-48d3-8455-3fe0cf2c0c5b">
| <img width="1261" height="946" alt="Screenshot 2025-07-10 155258"
src="https://github.com/user-attachments/assets/5d2d8a34-fb01-4c58-9fef-be3ad10b7cb0">
|

<!--
Are you targeting main? All PRs should target the main branch unless
otherwise noted.
-->

---------
…tnet#34383)

<!-- Please let the below note in for people that find this PR -->
> [!NOTE]
> Are you waiting for the changes in this PR to be merged?
> It would be very helpful if you could [test the resulting
artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!
<!--
!!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING
MAIN. !!!!!!!
-->
### Root Cause:
HTML text is applied asynchronously in .NET MAUI on iOS, causing the
CollectionView cell to render first and then re-measure after the HTML
is applied, which leads to visible resizing (scroll jitter).
### Description of Change
Improvements to HTML text handling in CollectionView:

* Updated `UpdateText` method in `LabelExtensions.cs` to check if the
`UILabel` is inside a CV2 cell using the new
`IsPlatformLabelInsideCV2Cell` method, allowing synchronous HTML text
updates when safe and avoiding unnecessary layout passes.
* Added the `IsPlatformLabelInsideCV2Cell` helper method to walk the
UIKit superview chain and detect CV2 cells, improving reliability and
preventing crashes in CV1 layouts.
* Added a reference to `Microsoft.Maui.Controls.Handlers.Items2` to
support the new CV2 cell detection logic.
<!-- Enter description of the fix in this section -->
### Why Tests were not added:
This bug occurs only during live scrolling of CollectionView cells on
iOS when a Label with TextType="Html" is rendered. The issue depends on
the precise timing of asynchronous HTML text application, which triggers
a second layout pass while cells are visible, causing scroll jitter.
Automated tests cannot reliably reproduce this because no framework can
simulate real-time scrolling and layout timing at the native speed
### Issues Fixed

<!-- Please make sure that there is a bug logged for the issue being
fixed. The bug should describe the problem and how to reproduce it. -->

Fixes dotnet#33065 

### Tested the behavior in the following platforms

- [x] Windows
- [x] Android
- [x] iOS
- [x] Mac

| Before Issue Fix | After Issue Fix |
|----------|----------|
| <video
src="https://github.com/user-attachments/assets/1045cbe3-e869-4e08-9562-8032dd9cea88">
| <video
src="https://github.com/user-attachments/assets/0c7b3bbb-4917-482f-ac7e-05e343de3ae0">
|


<!--
Are you targeting main? All PRs should target the main branch unless
otherwise noted.
-->
…4936)

## Summary

- For `Default` and `Fixed` `FlyoutHeaderBehavior`, the flyout scroll
view is now positioned below the header instead of overlapping it with a
content inset. This prevents items from rendering behind
semi-transparent headers when scrolling.
- `Scroll` and `CollapseOnScroll` behaviors are unchanged — they still
overlap the header so it can scroll away or shrink.
- Adds regression test
`FlyoutScrollViewDoesNotOverlapHeaderForDefaultAndFixed` and updates
`FlyoutHeaderContentAndFooterAllMeasureCorrectly` to match the new
layout for Default/Fixed.

Fixes dotnet#34925

## Changes

**`ShellFlyoutLayoutManager.cs`** (iOS):
- `SetHeaderContentInset()`: For Default/Fixed, sets `ContentInset.Top =
0` since the scroll view frame already starts below the header.
- `LayoutContent()`: For Default/Fixed, adds the full header height to
the content Y offset instead of just the margin.

**`ShellFlyoutTests.cs`**:
- New test: `FlyoutScrollViewDoesNotOverlapHeaderForDefaultAndFixed` —
verifies the scroll view frame starts at or below the header bottom for
Default and Fixed behaviors.
- Updated test: `FlyoutHeaderContentAndFooterAllMeasureCorrectly` —
adjusts iOS ScrollView expectations so that only Scroll/CollapseOnScroll
use content inset; Default/Fixed use frame positioning.

## Test plan

- [x] New regression test
`FlyoutScrollViewDoesNotOverlapHeaderForDefaultAndFixed` fails without
fix, passes with fix
- [x] Existing `FlyoutHeaderContentAndFooterAllMeasureCorrectly` test
passes with updated expectations
- [ ] Manual verification: open flyout with semi-transparent header and
15+ items, scroll down — items should not be visible behind header

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------
…t#28071)

<!-- Please let the below note in for people that find this PR -->
> [!NOTE]
> Are you waiting for the changes in this PR to be merged?
> It would be very helpful if you could [test the resulting
artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!


<!--
!!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING
MAIN. !!!!!!!
-->

### Issue Details
When a Margin value is set for the Path, it renders correctly. However,
when the Path is placed inside a StackLayout, it does not render.

### Root Cause
While measuring the Path, the size calculation does not account for the
Margin value, which leads to incorrect path rendering.

### Description of Change
To include the Margin value in size calculations and ensure proper Path
rendering.

Validated the behavior in the following platforms
 
- [x] Android
- [x] Windows
- [x] iOS
- [x] Mac
 
 
### Issues Fixed
  
Fixes dotnet#13801 

### Output  ScreenShot

| Before  | After  |
|---------|--------|
| <img width="501" alt="image (2)"
src="https://github.com/user-attachments/assets/7afdacbd-089b-47c2-bad9-216a19618bca"
/> | <img width="494" alt="image (3)"
src="https://github.com/user-attachments/assets/c64f2eeb-b9c0-4340-beb6-f35fd63f28fc"
/> |
…hicsView (dotnet#34557)

<!-- Please let the below note in for people that find this PR -->
> [!NOTE]
> Are you waiting for the changes in this PR to be merged?
> It would be very helpful if you could [test the resulting
artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!
<!--
!!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING
MAIN. !!!!!!!
-->
<!--
!!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING
MAIN. !!!!!!!
-->
### Issue Details
The FlowDirection property is not functioning as expected when applied
to Drawable controls and GraphicsView.
When FlowDirection is set to either RightToLeft or LeftToRight, there is
no observable change in layout behavior.

### Root Cause
ShapeViewHandler had no FlowDirection mapper, and ShapeDrawable.Draw()
never applied canvas mirroring.

### Description of Change
**Android**: Updated PlatformGraphicsView to mirror its content
horizontally when the layout direction is RTL by applying a translation
and scale transformation in the Draw method.
**iOS**: Modified PlatformGraphicsView to check
EffectiveUserInterfaceLayoutDirection and apply a horizontal flip
transformation when in RTL mode.
**Windows**: Changed PlatformGraphicsView to concatenate a scale and
translation transform for RTL flow direction before drawing content.

### Validated the behaviour in the following platforms
- [x] Android
- [x] Windows
- [x] iOS
- [x] Mac

### Issues Fixed:
Fixes dotnet#34402 

### Screenshots
| Before  | After |
|---------|--------|
|  <video
src="https://github.com/user-attachments/assets/62bec2c7-dbe5-4696-9f78-d2d1b4bcdaec">
|   <video
src="https://github.com/user-attachments/assets/d143819d-b525-455b-adeb-d589171de61e"> 
|
…t#34954)

<!-- Please let the below note in for people that find this PR -->
> [!NOTE]
> Are you waiting for the changes in this PR to be merged?
> It would be very helpful if you could [test the resulting
artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!
<!--
!!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING
MAIN. !!!!!!!
-->

### Issue Details

- HEIC images picked via PickPhotosAsync are not displayed — the image
appears blank, whereas other image formats (e.g., JPEG, PNG) display
correctly.

### Root Cause of the issue

- PR [34250](dotnet#34250) moved
CompletedHandler invocation into the DismissViewController completion
callback. This introduced a GC race condition where
PhotoPickerPresentationControllerDelegate.Dispose() fires
tcs.TrySetResult([]) before the async CompletedHandler finishes HEIC
transcoding.
- HEIC is particularly affected because
NSItemProvider.LoadDataRepresentationAsync transcoding is significantly
slower than JPEG/PNG loading, widening the GC window.

### Description of Change
**Race condition prevention:**

* In `PhotoPickerDelegate.DidFinishPicking`, the `Handler` property of
`PhotoPickerPresentationControllerDelegate` is set to `null` before
dismissing the picker to avoid a garbage collection race condition that
could interfere with the async completion handler, especially during
slow operations like HEIC transcoding.
<!-- Enter description of the fix in this section -->

### Issues Fixed
Fixes dotnet#34953

### Tested the behaviour in the following platforms

- [ ] - Windows 
- [ ] - Android
- [x] - iOS
- [ ] - Mac

| Before | After |
|----------|----------|
| <video
src="https://github.com/user-attachments/assets/368192e4-57ea-4732-82df-3e3ca386ab35">
| <video
src="https://github.com/user-attachments/assets/0e3a5066-e092-4461-9199-1730475307d8">
|

<!--
Are you targeting main? All PRs should target the main branch unless
otherwise noted.
-->
…ion on the second programmatic call (dotnet#34982)

<!-- Please let the below note in for people that find this PR -->
> [!NOTE]
> Are you waiting for the changes in this PR to be merged?
> It would be very helpful if you could [test the resulting
artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!

### Issue Details

The issue occurs when calling the SwipeView.Open() method
programmatically multiple times on iOS and MacCatalyst platforms. The
first call to open swipe items (such as RightItems or BottomItems) works
as expected, but the second call throws a System.ArgumentException with
the message “An item with the same key has already been added.”
 
This behavior is specific to certain swipe directions that rely on
negative offsets (such as right and bottom swipe actions), where the
swipe interaction briefly appears and then resets unexpectedly. As a
result, internal state inconsistencies lead to a crash during subsequent
calls.

### Root Cause

The issue occur because of an incorrect use of Math.Abs on the
_swipeOffset value inside the ProgrammaticallyOpenSwipeItem()method.
This operation removes the negative sign required for specific swipe
directions (such as left or up), causing the offset to become invalid
during layout validation.
 
Due to this invalid offset, the swipe view resets to a closed state
while still retaining previously added entries in the _swipeItems
dictionary. When Open() is called again, the same keys are added again
to the dictionary, resulting in a duplicate key exception.

### Description of Change

The fix involves removing the Math.Abs operation on _swipeOffset to
preserve the correct directional value required for swipe behavior,
ensuring that the swipe state remains consistent after layout
validation.
 
Additionally, a defensive improvement is introduced by clearing the
_swipeItems dictionary before repopulating it in the UpdateSwipeItems()
method. This prevents duplicate key insertion and ensures that the
method behaves safely even if invoked multiple times under unexpected
conditions.

### Issues Fixed
Fixes dotnet#34917

### Validated the behaviour in the following platforms

- [ ] Windows
- [ ] Android
- [x] iOS
- [x] Mac

### Output
| Before | After |
|----------|----------|
| <video
src="https://github.com/user-attachments/assets/55a131cb-6ccc-4776-80d7-90655651565d">
| <video
src="https://github.com/user-attachments/assets/5efc010e-04ec-4e4d-8729-d43f41d2d6bf">
|
…ng a NonFlyOutPage (dotnet#34839)

<!-- Please let the below note in for people that find this PR -->
> [!NOTE]
> Are you waiting for the changes in this PR to be merged?
> It would be very helpful if you could [test the resulting
artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!

### Issue Details:

Title of FlyOutPage is not updating anymore after showing a
NonFlyOutPage in Android Platform.
       
### Root Cause:

When Window.Page is swapped from a FlyoutPage to a plain ContentPage,
the Window.Page setter clears flyoutPage.Parent = null synchronously
before propagating the Window=null event through the element hierarchy.
This means by the time NavigationPage.OnWindowChanged(null) fires to
clean up the toolbar, the check flyoutPage.Parent is IWindow evaluates
to false (Parent is already null). As a result, flyoutPage.Toolbar =
null never executes — the stale, now-disconnected NavigationPageToolbar
remains attached to
FlyoutPage.Toolbar. When FlyoutPage is later restored as Window.Page,
FindMyToolbar() traverses the ancestors, finds the stale toolbar on
FlyoutPage.Toolbar, and returns early — no new connected toolbar is
created. Since the stale toolbar's ToolbarTracker subscriptions were
severed by Disconnect(), it never receives CurrentPage change
notifications, so the title is permanently frozen.

### Description of Change:

Remove the flyoutPage.Parent is IWindow && condition from the guard in
NavigationPage.OnWindowChanged(null) in NavigationPage.cs. The remaining
check flyoutPage.Toolbar == _toolbar is the correct and sufficient
invariant — it ensures we only clear the toolbar that this
NavigationPage created. The Parent is IWindow guard was redundant in the
normal case (when FlyoutPage is the root page, Parent is IWindow), but
fatally incorrect during a Window.Page swap because the ordering
guarantee it depended on didn't hold.

**Tested the behavior in the following platforms:**

- [x] Android
- [ ] Windows
- [x] iOS
- [x] Mac

### Reference:

N/A

### Issues Fixed:

Fixes  dotnet#33615         

### Screenshots
| Before  | After  |
|---------|--------|
| <Video
src="https://github.com/user-attachments/assets/00869428-8cb4-43a8-981b-7ac6b018e184"
Width="300" Height="600"> | <Video
src="https://github.com/user-attachments/assets/81f94d6d-8c25-4d08-a699-4b3db32e76c8"
Width="300" Height="600"> |
…otnet#34970)

<!-- Please keep the note below for people who find this PR -->
> [!NOTE]
> Are you waiting for the changes in this PR to be merged?
> It would be very helpful if you could [test the resulting
artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment whether this change resolves your
issue. Thank you!
<!--
!!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING
MAIN. !!!!!!!
-->

This pull request addresses the issue where the `DatePicker` control on
MacCatalyst did not correctly raise its `Opened` and `Closed` events.
The changes implement a more robust mechanism for detecting when the
DatePicker is opened and closed, specifically for MacCatalyst, and add
new tests to verify this behavior.
### Description of Change :

**MacCatalyst DatePicker Event Handling Improvements:**

* Added logic to traverse the internal view hierarchy of the compact
`UIDatePicker` to find and wire up `UITextField` subviews, allowing
detection of when the DatePicker is opened via the `EditingDidBegin`
event. A one-shot observer is registered to detect when the picker
popover window closes, ensuring the `Closed` event is raised reliably.
(`DatePickerHandler.MacCatalyst.cs`)
* Implemented cleanup logic to unwire event handlers and remove
observers during disconnect, preventing memory leaks and spurious event
firing. (`DatePickerHandler.MacCatalyst.cs`)
* Removed previous event handler attachments
(`EditingDidBegin`/`EditingDidEnd`) from the proxy, as the new mechanism
supersedes them. (`DatePickerHandler.MacCatalyst.cs`)
[[1]](diffhunk://#diff-2107542f6f788907263db46eab6a80232ed765aa515806945e8f65681c8421d1L105-L113)
[[2]](diffhunk://#diff-2107542f6f788907263db46eab6a80232ed765aa515806945e8f65681c8421d1L125-L136)

**Testing Enhancements:**

* Added a new test case page (`Issue34848`) and a corresponding UI test
to verify that the `Opened` and `Closed` events are raised correctly on
MacCatalyst and other platforms, using platform-specific logic to close
the DatePicker. (`TestCases.HostApp/Issues/Issue34848.cs`,
`TestCases.Shared.Tests/Tests/Issues/Issue34848.cs`)
[[1]](diffhunk://#diff-652f2cd8a1e252cf8db29bf33034066d08ec5e3b43ec76a77d477824a08a1f44R1-R46)
[[2]](diffhunk://#diff-a57ba10bf75c97b2a6f28c075585a1066df8c7c2c31751e3c799177fca6560b4R1-R42)

**General Codebase Improvements:**

* Minor code cleanup and improved organization in the DatePicker handler
for MacCatalyst, including the addition of necessary using directives.
(`DatePickerHandler.MacCatalyst.cs`)


<!-- Enter description of the fix in this section -->

### Issues Fixed

<!-- Please make sure that there is a bug logged for the issue being
fixed. The bug should describe the problem and how to reproduce it. -->

Fixes dotnet#34848 

### Tested the behavior in the following platforms

- [ ] Windows
- [ ] Android
- [ ] iOS
- [x] Mac

| Before Issue Fix | After Issue Fix |
|----------|----------|
| <video
src="https://github.com/user-attachments/assets/73c4686f-30d0-4a57-bb8d-be6b2d4b7cda">
| <video
src="https://github.com/user-attachments/assets/65d6f44e-b145-48de-b41a-77b6abefabc4">
|
<!--
Are you targeting main? All PRs should target the main branch unless
otherwise noted.
-->
@kubaflo
Copy link
Copy Markdown
Contributor

kubaflo commented Jun 2, 2026

/review -b feature/enhanced-reviewer

@kubaflo
Copy link
Copy Markdown
Contributor

kubaflo commented Jun 2, 2026

/azp run

@azure-pipelines
Copy link
Copy Markdown

Azure Pipelines successfully started running 3 pipeline(s).

Copy link
Copy Markdown
Collaborator

@MauiBot MauiBot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Expert Review — 2 findings

See inline comments for details.

Comment thread src/Core/src/Handlers/Switch/SwitchHandler.iOS.cs Outdated
Comment thread src/Core/src/Handlers/Switch/SwitchHandler.iOS.cs
@MauiBot MauiBot added s/agent-fix-pr-picked AI could not beat the PR fix - PR is the best among all candidates and removed s/agent-fix-win AI found a better alternative fix than the PR labels Jun 2, 2026
MauiBot

This comment was marked as outdated.

@kubaflo
Copy link
Copy Markdown
Contributor

kubaflo commented Jun 2, 2026

Test Failure Review

@AdamEssenmacher — new test-failure review results are available based on this last commit: 1a4ae29.
To request a fresh review after new comments, commits, or CI runs, comment /review tests.

Overall Insufficient data Failures 3 Data Partial Platform mixed

Test Failure Review1a4ae29 · [iOS] Fix Switch custom colors on iOS 26 · 2026-06-02T14:08:58Z

Overall verdict: Insufficient data

The completed maui-pr and maui-pr-devicetests checks are green, but maui-pr-uitests build 1445326 was still inProgress/queued when the context was gathered and several UI-test shards were still running. The only extracted test-result failures are Roslyn/CoreCLR/GC runtime failures on Windows/unknown platforms, which do not match this PR's iOS Switch handler, iOS snapshots, or Switch device-test scope.

Failure Verdict Evidence
maui-pr-uitests build 1445326 incomplete UI-test run Insufficient data Build 1445326 is maui-pr-uitests (definition 313) on refs/pull/35385/merge, status inProgress, result null. The rollup check is QUEUED, and child checks for Android CollectionView, iOS CollectionView1, iOS SearchBar/Shape/Slider, macOS CarouselView, and macOS CollectionView are still IN_PROGRESS, so the UI-test result is not final.
maui-pr-uitests timeline task issues Insufficient data Six timeline records contain only PowerShell exited with code '1'. plus retry warnings: Controls WebView, Controls Page,Performance,Picker,ProgressBar, Controls (vlatest) Border,BoxView,Brush,Button, Controls Lifecycle,ManualReview,Maps, Controls (vlatest) Editor,Effects,Essentials,FlyoutPage,Focus,Fonts,Frame,Gestures,GraphicsView, and Controls ListView. Each record has result: succeeded after retry and no log id/log excerpt in the gathered context, so there is no test name, stack trace, or failure message to attribute or dismiss.
53 raw / 23 unique authenticated AzDO test-result failures Likely unrelated The extracted failures are Roslyn/CodeAnalysis, JIT, readytorun/crossgen2, and GC tests such as Microsoft.CodeAnalysis.*OrganizeImports*, JIT/Performance/CodeQuality/Roslyn/CscBench, readytorun/*crossgen2determinism*, and GC/GetTotalPauseDuration on Windows/unknown platforms. They do not reference changed files or tests; this PR changes iOS/macOS Switch handler code, iOS 26 visual snapshots, and SwitchHandlerTests.iOS.cs. Recent main-branch runs of the same UI-test definition also include failures: 1444191, 1431731, 1419912, and 1419503 failed.

Recommended action

Regather the review context after maui-pr-uitests build 1445326 completes so the final UI shard results and logs are available; do not attribute the Roslyn/CoreCLR/GC test-result failures to this PR unless new evidence ties them to the changed iOS Switch files.

Evidence details
  • Context generated: 2026-06-02T14:08:58.7437180Z for PR [iOS] Fix Switch custom colors on iOS 26 #35385.
  • PR metadata: head commit 1a4ae2963237d31f2d3eb052039c1ad3f2fa0a22; base main; labels community ✨, s/agent-reviewed, and s/agent-fix-pr-picked.
  • PR scope from changed files: inferred platforms ios, macos; area hints Handler, DeviceTests; changed files include src/Core/src/Handlers/Switch/SwitchHandler.iOS.cs, src/Core/src/Platform/iOS/MauiSwitch.cs, src/Core/src/Platform/iOS/SwitchExtensions.cs, src/Core/tests/DeviceTests/Handlers/Switch/SwitchHandlerTests.iOS.cs, and iOS 26 snapshots for CheckBox, DatePicker, Editor, RadioButton, SearchBar, Slider, Switch, and visual state tests.
  • Completed checks in the gathered context: maui-pr build 1445325 succeeded, and maui-pr-devicetests build 1445327 succeeded.
  • Device-test limitation: no Helix aggregate data was included for maui-pr-devicetests, so hidden XHarness/Helix failures could not be independently verified from this context.
  • maui-pr-uitests build 1445326 metadata: build number 10.0.80-ci+pr.35385.azdo.1445326, definition maui-pr-uitests (313), status inProgress, result null, source branch refs/pull/35385/merge, source version 25891cebad3019e93c84bef6cbdd9756940f379e, started 2026-06-02T13:59:45.695986Z.
  • Inconclusive UI-test checks at gather time: maui-pr-uitests rollup QUEUED; Build Analysis IN_PROGRESS; Android CollectionView, iOS CollectionView1, iOS SearchBar/Shape/Slider, macOS CarouselView, and macOS CollectionView child checks IN_PROGRESS.
  • Timeline task issues in build 1445326: six task records had PowerShell exited with code '1'. and RetryHelper encountered task failure, will retry (attempt #: 1 out of 1) after 1000 ms, but the task records are marked result: succeeded and have no log id attached in the gathered context.
  • Authenticated AzDO test results for build 1445326 returned 53 raw failures, deduplicated to 23 unique test/platform groups. Representative unrelated groups include Roslyn OrganizeImports case-sensitivity assertions, JIT ssaNum != SsaConfig::RESERVED_SSA_NUM assertions, readytorun/crossgen2 determinism crashes, and GC GetTotalPauseDuration failures across run IDs including 119522, 121634, 129430, 129438, 129640, 129780, and 129808.
  • Recent base-branch comparison for maui-pr-uitests: build 1444191 failed, 1432188 succeeded, 1431731 failed, 1419912 failed, and 1419503 failed on refs/heads/main.
  • Data limitation: authenticated AzDO access used an Azure CLI bearer token for local-only data gathering; the gh-aw workflow relies on public build/timeline/log APIs unless AZDO_TOKEN is provided by the runner environment.

# Conflicts:
#	src/Controls/tests/TestCases.iOS.Tests/snapshots/ios-26/LightTheme_CheckBox_VerifyVisualState.png
#	src/Controls/tests/TestCases.iOS.Tests/snapshots/ios-26/LightTheme_DatePicker_VerifyVisualState.png
#	src/Controls/tests/TestCases.iOS.Tests/snapshots/ios-26/LightTheme_Editor_VerifyVisualState.png
#	src/Controls/tests/TestCases.iOS.Tests/snapshots/ios-26/LightTheme_RadioButton_VerifyVisualState.png
#	src/Controls/tests/TestCases.iOS.Tests/snapshots/ios-26/LightTheme_SearchBar_VerifyVisualState.png
#	src/Controls/tests/TestCases.iOS.Tests/snapshots/ios-26/LightTheme_Slider_VerifyVisualState.png
#	src/Controls/tests/TestCases.iOS.Tests/snapshots/ios-26/LightTheme_Switch_VerifyVisualState.png
#	src/Controls/tests/TestCases.iOS.Tests/snapshots/ios-26/LightTheme_VerifyVisualState.png
#	src/Core/src/Handlers/Switch/SwitchHandler.iOS.cs
@kubaflo
Copy link
Copy Markdown
Contributor

kubaflo commented Jun 2, 2026

/review -b feature/enhanced-reviewer

@MauiBot

This comment has been minimized.

@kubaflo
Copy link
Copy Markdown
Contributor

kubaflo commented Jun 3, 2026

/review -b feature/enhanced-reviewer

@MauiBot

This comment has been minimized.

@kubaflo
Copy link
Copy Markdown
Contributor

kubaflo commented Jun 3, 2026

/review -b feature/enhanced-reviewer

Copy link
Copy Markdown
Collaborator

@MauiBot MauiBot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Expert Review — 1 findings

See inline comments for details.


try
{
this.ApplyTrackColor(virtualView);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[major] Handler Mapper and Property Patterns — This lifecycle reapply path writes TrackColor/ThumbColor directly via ApplyTrackColor/ApplyThumbColor, so any app or library customization registered with SwitchHandler.Mapper.AppendToMapping/PrependToMapping for TrackColor or ThumbColor is skipped after MovedToWindow/LayoutSubviews/TraitCollectionDidChange. Concrete scenario: an app appends a mapping that adjusts the native switch color after MAUI maps TrackColor; it works initially, but an iOS 26 trait/window reapply overwrites it with the raw virtual-view colors. Please route reapply through the handler mapper (for example Handler.UpdateValue for the affected properties) or otherwise preserve mapper customizations.

Copy link
Copy Markdown
Collaborator

@MauiBot MauiBot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI Review Summary

@AdamEssenmacher — new AI review results are available based on this last commit: b707a1c.
** [MacCatalyst] Fix DatePicker Opened/Closed events not being raised (#34970)** To request a fresh review after new comments or commits, comment /review rerun.

Gate Failed Code Review In Review Confidence Medium Platform Android

Review Sessions — click to expand
Gate — Test Before & After Fix

Gate Result: ❌ FAILED

Platform: ANDROID · Base: main · Merge base: e904e900

🩺 Fix does not pass the tests — every test still fails after applying the fix. The PR's change does not resolve the failure(s).

Test Without Fix (expect FAIL) With Fix (expect PASS)
📱 SwitchHandlerTests (DefaultSwitchReappliesLegacyOffTrackColorBeforeiOSOrMacCatalyst26, CustomColorsUseSlidingStyleOniOSOrMacCatalyst26, CustomColorsRenderOnInitialOffStateOniOSOrMacCatalyst26, DefaultSwitchUsesAutomaticStyleOniOSOrMacCatalyst26, ThumbColorClearsWhenResetOniOSOrMacCatalyst26, CustomColorsClearToAutomaticStyleOniOSOrMacCatalyst26, CustomColorsReapplyAfterMovedToWindowOniOSOrMacCatalyst26, QueuedColorReapplyDoesNotUpdateStaleNativeSwitchAfterReconnectOniOSOrMacCatalyst26, CustomColorsReapplyAfterWillEnterForegroundOniOS26, CustomColorsUpdateAfterAppThemeChangeOniOSOrMacCatalyst26) Category=Switch ✅ FAIL — 506s ❌ FAIL — 651s
🔴 Without fix — 📱 SwitchHandlerTests (DefaultSwitchReappliesLegacyOffTrackColorBeforeiOSOrMacCatalyst26, CustomColorsUseSlidingStyleOniOSOrMacCatalyst26, CustomColorsRenderOnInitialOffStateOniOSOrMacCatalyst26, DefaultSwitchUsesAutomaticStyleOniOSOrMacCatalyst26, ThumbColorClearsWhenResetOniOSOrMacCatalyst26, CustomColorsClearToAutomaticStyleOniOSOrMacCatalyst26, CustomColorsReapplyAfterMovedToWindowOniOSOrMacCatalyst26, QueuedColorReapplyDoesNotUpdateStaleNativeSwitchAfterReconnectOniOSOrMacCatalyst26, CustomColorsReapplyAfterWillEnterForegroundOniOS26, CustomColorsUpdateAfterAppThemeChangeOniOSOrMacCatalyst26): FAIL ✅ · 506s

(truncated to last 15,000 chars)

8Ho/H4/F4PB6Px+PxeNoTudAn8P+c/P13F+wscnhBNJf57q80rOfFMJ8wzotgvCCWn8bCXmi9kfnEsRjRLBkviOWh8T5Kw6eaZ9v5XEZ+3TZ81yiGZRWHF8TSaSzgvAjywpAfAxWDXF777Mr9dwJcCO49sCG4V1IhOGYLIS+OplgML4hzZ64nXTWurwPpA9UHcgz4XeAWkCLoXhABceBGwVXBPAPub4A1wAi4EbCDdQHkBdFoORrXl3xRnsXTKAYFyGaQPaA+BvIBEIO6Cwq/BX290HcCLumA9WVYq2BlAEUHIRAbqFo4VYHDU/D+Kjg0CiN/DyMPQhSCvRR4sS6GvEiWTRReEGfPLDdAThBrQa8HdxsEt8LAari2CB8pw/UhXCmwSkEnUJb0P87VdyguLdyKhUkHJxJ4awp2TsGOEdj9NAw9Bsn7IIfBcLo45oszzvriPIsjCw5niWEt6MPg/gD4FdhwDdysYFsI1ytYDxTIFZKQlqICV/vEgqjaRrl6qACRhfdj2Gnh+TfhuYfgwF8Da+vCmEsUmUs5K7wgFsd8VkFthiCC+DroehDuKMDtCrYo6AOC3H9
      06-03 05:57:35.821  8666  8827 I DOTNET  :    Exception stack traces:    at Microsoft.Maui.DeviceTests.AssertionExtensions.<>c__DisplayClass32_0.<AssertContainsColor>b__0()
      06-03 05:57:35.821  8666  8827 I DOTNET  :    at System.Threading.Tasks.Task`1[[Android.Graphics.Bitmap, Mono.Android, Version=0.0.0.0, Culture=neutral, PublicKeyToken=84e04ff9cfb79065]].InnerInvoke()
      06-03 05:57:35.821  8666  8827 I DOTNET  :    at System.Threading.Tasks.Task.<>c.<.cctor>b__288_0(Object obj)
      06-03 05:57:35.821  8666  8827 I DOTNET  :    at System.Threading.ExecutionContext.RunFromThreadPoolDispatchLoop(Thread , ExecutionContext , ContextCallback , Object )
      06-03 05:57:35.821  8666  8827 I DOTNET  : --- End of stack trace from previous location ---
      06-03 05:57:35.821  8666  8827 I DOTNET  :    at System.Threading.ExecutionContext.RunFromThreadPoolDispatchLoop(Thread , ExecutionContext , ContextCallback , Object )
      06-03 05:57:35.821  8666  8827 I DOTNET  :    at System.Threading.Tasks.Task.ExecuteWithThreadLocal(Task& , Thread )
      06-03 05:57:35.821  8666  8827 I DOTNET  : --- End of stack trace from previous location ---
      06-03 05:57:35.821  8666  8827 I DOTNET  :    at Microsoft.Maui.DeviceTests.AssertionExtensions.AssertContainsColor(View view, Color expectedColor, IMauiContext mauiContext, Nullable`1 tolerance)
      06-03 05:57:35.821  8666  8827 I DOTNET  :    at Microsoft.Maui.DeviceTests.HandlerTestBasement.<>c__DisplayClass18_0.<<ValidateHasColor>b__0>d.MoveNext()
      06-03 05:57:35.821  8666  8827 I DOTNET  : --- End of stack trace from previous location ---
      06-03 05:57:35.821  8666  8827 I DOTNET  :    at Microsoft.Maui.Dispatching.DispatcherExtensions.<>c__DisplayClass3_0.<<DispatchAsync>b__0>d.MoveNext()
      06-03 05:57:35.821  8666  8827 I DOTNET  : --- End of stack trace from previous location ---
      06-03 05:57:35.821  8666  8827 I DOTNET  :    at Microsoft.Maui.Dispatching.DispatcherExtensions.<>c__DisplayClass2_0`1.<<DispatchAsync>b__0>d[[System.Boolean, System.Private.CoreLib, Version=10.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].MoveNext()
      06-03 05:57:35.821  8666  8827 I DOTNET  : --- End of stack trace from previous location ---
      06-03 05:57:35.821  8666  8827 I DOTNET  :    at Microsoft.Maui.DeviceTests.SwitchHandlerTests.ThumbColorUpdatesCorrectly()
      06-03 05:57:35.821  8666  8827 I DOTNET  : --- End of stack trace from previous location ---
      06-03 05:57:35.821  8666  8827 I DOTNET  :    Execution time: 0.2967801
      06-03 05:57:35.821  8666  8827 I DOTNET  :    Test trait name: Category
      06-03 05:57:35.821  8666  8827 I DOTNET  :       value: Switch
      06-03 05:57:35.821  8666  8827 I DOTNET  : 	[FAIL] Thumb Color Updates Correctly
      06-03 05:57:35.821  8666  8827 I DOTNET  : 	[FAIL] Thumb Color Updates Correctly   Test name: Thumb Color Updates Correctly
      06-03 05:57:35.821  8666  8827 I DOTNET  :    Assembly:  [Microsoft.Maui.Core.DeviceTests, Version=10.0.0.0, Culture=neutral, PublicKeyToken=null]
      06-03 05:57:35.821  8666  8827 I DOTNET  :    Exception messages: Color Color [A=255, R=255, G=0, B=0] not found. This is what it looked like:<img>iVBORw0KGgoAAAANSUhEUgAAAIQAAACECAYAAABRRIOnAAAAAXNSR0IArs4c6QAAAARzQklUCAgICHwIZIgAAA/rSURBVHic7Z17jB3Vfcc/v3Nm7mMf9u567QVjMDYuD2NMk7agKGkxhTTpU6iCVG1FGlo1UdWmUtWHQqMKXKlVioRKojRRWjVUrdSWh1pUgigtLyUNJBQwxtjYYDCY9WO99q69r7t3Zs45/WPu7J293vWuvXvte+n5SKM7unfuvM53fr/f+Z3HgMfj8Xg8Ho/H4/F4PB6Px+PxeDwej8fj8Xg8Ho/H4/F4PB6Px+PxeDwej8fj8Xg8Ho/H4/F4PB6Px+PxeNoTudAn8P+c/P13F+wscnhBNJf57q80rOfFMJ8wzotgvCCWn8bCXmi9kfnEsRjRLBkviOWh8T5Kw6eaZ9v5XEZ+3TZ81yiGZRWHF8TSaSzgvAjywpAfAxWDXF777Mr9dwJcCO49sCG4V1IhOGYLIS+OplgML4hzZ64nXTWurwPpA9UHcgz4XeAWkCLoXhABceBGwVXBPAPub4A1wAi4EbCDdQHkBdFoORrXl3xRnsXTKAYFyGaQPaA+BvIBEIO6Cwq/BX290HcCLumA9WVYq2BlAEUHIRAbqFo4VYHDU/D+Kjg0CiN/DyMPQhSCvRR4sS6GvEiWTRReEGfPLDdAThBrQa8HdxsEt8LAari2CB8pw/UhXCmwSkEnUJb0P87VdyguLdyKhUkHJxJ4awp2TsGOEdj9NAw9Bsn7IIfBcLo45oszzvriPIsjCw5niWEt6MPg/gD4FdhwDdysYFsI1ytYDxTIFZKQlqICV/vEgqjaRrl6qACRhfdj2Gnh+TfhuYfgwF8Da+vCmEsUmUs5K7wgFsd8VkFthiCC+DroehDuKMDtCrYo6AOC3H9
      06-03 05:57:35.862  8666  8827 I DOTNET  :    at System.Threading.Tasks.Task.<>c.<.cctor>b__288_0(Object obj)
      06-03 05:57:35.862  8666  8827 I DOTNET  :    at System.Threading.ExecutionContext.RunFromThreadPoolDispatchLoop(Thread , ExecutionContext , ContextCallback , Object )
      06-03 05:57:35.862  8666  8827 I DOTNET  : --- End of stack trace from previous location ---
      06-03 05:57:35.862  8666  8827 I DOTNET  :    at System.Threading.ExecutionContext.RunFromThreadPoolDispatchLoop(Thread , ExecutionContext , ContextCallback , Object )
      06-03 05:57:35.862  8666  8827 I DOTNET  :    at System.Threading.Tasks.Task.ExecuteWithThreadLocal(Task& , Thread )
      06-03 05:57:35.862  8666  8827 I DOTNET  : --- End of stack trace from previous location ---
      06-03 05:57:35.862  8666  8827 I DOTNET  :    at Microsoft.Maui.DeviceTests.AssertionExtensions.AssertContainsColor(View view, Color expectedColor, IMauiContext mauiContext, Nullable`1 tolerance)
      06-03 05:57:35.862  8666  8827 I DOTNET  :    at Microsoft.Maui.DeviceTests.HandlerTestBasement.<>c__DisplayClass18_0.<<ValidateHasColor>b__0>d.MoveNext()
      06-03 05:57:35.862  8666  8827 I DOTNET  : --- End of stack trace from previous location ---
      06-03 05:57:35.862  8666  8827 I DOTNET  :    at Microsoft.Maui.Dispatching.DispatcherExtensions.<>c__DisplayClass3_0.<<DispatchAsync>b__0>d.MoveNext()
      06-03 05:57:35.862  8666  8827 I DOTNET  : --- End of stack trace from previous location ---
      06-03 05:57:35.862  8666  8827 I DOTNET  :    at Microsoft.Maui.Dispatching.DispatcherExtensions.<>c__DisplayClass2_0`1.<<DispatchAsync>b__0>d[[System.Boolean, System.Private.CoreLib, Version=10.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].MoveNext()
      06-03 05:57:35.862  8666  8827 I DOTNET  : --- End of stack trace from previous location ---
      06-03 05:57:35.862  8666  8827 I DOTNET  :    at Microsoft.Maui.DeviceTests.SwitchHandlerTests.ThumbColorUpdatesCorrectly()
      06-03 05:57:35.862  8666  8827 I DOTNET  : --- End of stack trace from previous location ---
      06-03 05:57:35.862  8666  8827 I DOTNET  :    Execution time: 0.2967801
      06-03 05:57:35.862  8666  8827 I DOTNET  :    Test trait name: Category
      06-03 05:57:35.862  8666  8827 I DOTNET  :       value: Switch
      06-03 05:57:35.862  8666  8827 I DOTNET  : 
      06-03 05:57:35.875  8666  8827 I DOTNET  : 	[PASS] RotationX Initialize Correctly
      06-03 05:57:35.880  8666  8827 I DOTNET  : 	[PASS] RotationX Initialize Correctly
      06-03 05:57:35.891  8666  8827 I DOTNET  : 	[PASS] Null Thumb Color Doesn't Crash
      06-03 05:57:35.905  8666  8827 I DOTNET  : 	[PASS] Setting Semantic Description makes element accessible
      06-03 05:57:35.915  8666  8827 I DOTNET  : 	[PASS] Is Toggled Initializes Correctly
      06-03 05:57:35.949  8666  8827 I DOTNET  : 	[PASS] FlowDirection is set correctly
      06-03 05:57:35.961  8666  8827 I DOTNET  : 	[PASS] FlowDirection is set correctly
      06-03 05:57:36.011  8666  8827 I DOTNET  : 	[PASS] Opacity is set correctly
      06-03 05:57:36.026  8666  8827 I DOTNET  : 	[PASS] Opacity is set correctly
      06-03 05:57:36.026  8666  8827 I DOTNET  : 	[IGNORED] Semantic Hint is set correctly
      06-03 05:57:36.030  8666  8827 I DOTNET  : 	[PASS] DisconnectHandlerDoesntCrash
      06-03 05:57:36.033  8666  8827 I DOTNET  : 	[PASS] HandlersHaveAllExpectedContructors
      06-03 05:57:36.052  8666  8827 I DOTNET  : 	[PASS] Rotation Initialize Correctly
      06-03 05:57:36.057  8666  8827 I DOTNET  : 	[PASS] Rotation Initialize Correctly
      06-03 05:57:36.072  8666  8827 I DOTNET  : 	[PASS] MinimumHeightInitializes
      06-03 05:57:36.074  8666  8827 I DOTNET  : 	[PASS] MinimumHeightInitializes
      06-03 05:57:36.102  8666  8827 I DOTNET  : 	[PASS] Visibility is set correctly
      06-03 05:57:36.106  8666  8827 I DOTNET  : 	[PASS] Visibility is set correctly
      06-03 05:57:36.117  8666  8827 I DOTNET  : 	[PASS] Updating Native Is On property updates Virtual View
      06-03 05:57:36.120  8666  8827 I DOTNET  : 	[PASS] Native View Bounds are not empty
      06-03 05:57:36.122  8666  8827 I DOTNET  : 	[PASS] Native View Bounds are not empty
      06-03 05:57:36.127  8666  8827 I DOTNET  : 	[PASS] ScaleY Initialize Correctly
      06-03 05:57:36.129  8666  8827 I DOTNET  : 	[PASS] ScaleY Initialize Correctly
      06-03 05:57:36.133  8666  8827 I DOTNET  : 	[PASS] Clip Initializes ContainerView Correctly
      06-03 05:57:36.228  8666  8827 I DOTNET  : 	[PASS] Automation Id is set correctly
      06-03 05:57:36.233  8666  8827 I DOTNET  : 	[PASS] Null Semantics Doesnt throw exception
      06-03 05:57:36.246  8666  8827 I DOTNET  : 	[PASS] Semantic Heading is set correctly
      06-03 05:57:36.262  8666  8827 I DOTNET  : 	[PASS] Setting Semantic Hint makes element accessible
      06-03 05:57:36.304  8666  8827 I DOTNET  : Microsoft.Maui.DeviceTests.SwitchHandlerTests 1.8459761 ms
      06-03 05:57:36.320  8666  8747 I DOTNET  : Failed tests:
      06-03 05:57:36.321  8666  8747 I DOTNET  : 1) 	[FAIL] Thumb Color Updates Correctly   Test name: Thumb Color Updates Correctly
      06-03 05:57:36.321  8666  8747 I DOTNET  :    Assembly:  [Microsoft.Maui.Core.DeviceTests, Version=10.0.0.0, Culture=neutral, PublicKeyToken=null]
      06-03 05:57:36.321  8666  8747 I DOTNET  :    Exception messages: Color Color [A=255, R=255, G=0, B=0] not found. This is what it looked like:<img>iVBORw0KGgoAAAANSUhEUgAAAIQAAACECAYAAABRRIOnAAAAAXNSR0IArs4c6QAAAARzQklUCAgICHwIZIgAAA/rSURBVHic7Z17jB3Vfcc/v3Nm7mMf9u567QVjMDYuD2NMk7agKGkxhTTpU6iCVG1FGlo1UdWmUtWHQqMKXKlVioRKojRRWjVUrdSWh1pUgigtLyUNJBQwxtjYYDCY9WO99q69r7t3Zs45/WPu7J293vWuvXvte+n5SKM7unfuvM53fr/f+Z3HgMfj8Xg8Ho/H4/F4PB6Px+PxeDwej8fj8Xg8Ho/H4/F4PB6Px+PxeDwej8fj8Xg8Ho/H4/F4PB6Px+PxeNoTudAn8P+c/P13F+wscnhBNJf57q80rOfFMJ8wzotgvCCWn8bCXmi9kfnEsRjRLBkviOWh8T5Kw6eaZ9v5XEZ+3TZ81yiGZRWHF8TSaSzgvAjywpAfAxWDXF777Mr9dwJcCO49sCG4V1IhOGYLIS+OplgML4hzZ64nXTWurwPpA9UHcgz4XeAWkCLoXhABceBGwVXBPAPub4A1wAi4EbCDdQHkBdFoORrXl3xRnsXTKAYFyGaQPaA+BvIBEIO6Cwq/BX290HcCLumA9WVYq2BlAEUHIRAbqFo4VYHDU/D+Kjg0CiN/DyMPQhSCvRR4sS6GvEiWTRReEGfPLDdAThBrQa8HdxsEt8LAari2CB8pw/UhXCmwSkEnUJb0P87VdyguLdyKhUkHJxJ4awp2TsGOEdj9NAw9Bsn7IIfBcLo45oszzvriPIsjCw5niWEt6MPg/gD4FdhwDdysYFsI1ytYDxTIFZKQlqICV/vEgqjaRrl6qACRhfdj2Gnh+TfhuYfgwF8Da+vCmEsUmUs5K7wgFsd8VkFthiCC+DroehDuKMDtCrYo6AOC3H9
      06-03 05:57:36.321  8666  8747 I DOTNET  :    at System.Threading.Tasks.Task.<>c.<.cctor>b__288_0(Object obj)
      06-03 05:57:36.321  8666  8747 I DOTNET  :    at System.Threading.ExecutionContext.RunFromThreadPoolDispatchLoop(Thread , ExecutionContext , ContextCallback , Object )
      06-03 05:57:36.321  8666  8747 I DOTNET  : --- End of stack trace from previous location ---
      06-03 05:57:36.321  8666  8747 I DOTNET  :    at System.Threading.ExecutionContext.RunFromThreadPoolDispatchLoop(Thread , ExecutionContext , ContextCallback , Object )
      06-03 05:57:36.321  8666  8747 I DOTNET  :    at System.Threading.Tasks.Task.ExecuteWithThreadLocal(Task& , Thread )
      06-03 05:57:36.321  8666  8747 I DOTNET  : --- End of stack trace from previous location ---
      06-03 05:57:36.321  8666  8747 I DOTNET  :    at Microsoft.Maui.DeviceTests.AssertionExtensions.AssertContainsColor(View view, Color expectedColor, IMauiContext mauiContext, Nullable`1 tolerance)
      06-03 05:57:36.321  8666  8747 I DOTNET  :    at Microsoft.Maui.DeviceTests.HandlerTestBasement.<>c__DisplayClass18_0.<<ValidateHasColor>b__0>d.MoveNext()
      06-03 05:57:36.321  8666  8747 I DOTNET  : --- End of stack trace from previous location ---
      06-03 05:57:36.321  8666  8747 I DOTNET  :    at Microsoft.Maui.Dispatching.DispatcherExtensions.<>c__DisplayClass3_0.<<DispatchAsync>b__0>d.MoveNext()
      06-03 05:57:36.321  8666  8747 I DOTNET  : --- End of stack trace from previous location ---
      06-03 05:57:36.321  8666  8747 I DOTNET  :    at Microsoft.Maui.Dispatching.DispatcherExtensions.<>c__DisplayClass2_0`1.<<DispatchAsync>b__0>d[[System.Boolean, System.Private.CoreLib, Version=10.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].MoveNext()
      06-03 05:57:36.321  8666  8747 I DOTNET  : --- End of stack trace from previous location ---
      06-03 05:57:36.321  8666  8747 I DOTNET  :    at Microsoft.Maui.DeviceTests.SwitchHandlerTests.ThumbColorUpdatesCorrectly()
      06-03 05:57:36.321  8666  8747 I DOTNET  : --- End of stack trace from previous location ---
      06-03 05:57:36.321  8666  8747 I DOTNET  :    Execution time: 0.2967801
      06-03 05:57:36.321  8666  8747 I DOTNET  :    Test trait name: Category
      06-03 05:57:36.321  8666  8747 I DOTNET  :       value: Switch
      06-03 05:57:36.321  8666  8747 I DOTNET  : 
      06-03 05:57:36.413  8666  8747 I DOTNET  : Xml file was written to the provided writer.
      06-03 05:57:36.414  8666  8747 I DOTNET  : Tests run: 1314 Passed: 66 Inconclusive: 0 Failed: 1 Ignored: 1244
�[41m�[30mfail�[39m�[22m�[49m: Non-success instrumentation exit code: 1, expected: 0
�[40m�[32minfo�[39m�[22m�[49m: <<XHARNESS_RESULT_START>>
      {
        "version": 1,
        "machineName": "runnervmm79r7",
        "exitCode": 1,
        "exitCodeName": "TESTS_FAILED",
        "platform": "android",
        "instrumentationExitCode": 1,
        "device": "emulator-5554",
        "deviceOsVersion": "API 30",
        "architecture": "x86_64",
        "files": [
          {
            "name": "testResults.xml",
            "type": "test-results"
          },
          {
            "name": "adb-logcat-com.microsoft.maui.core.devicetests-default.log",
            "type": "logcat"
          }
        ]
      }
      <<XHARNESS_RESULT_END>>
�[40m�[32minfo�[39m�[22m�[49m: Attempting to remove apk 'com.microsoft.maui.core.devicetests'..
�[40m�[37mdbug�[39m�[22m�[49m: Executing command: '/home/vsts/.nuget/packages/microsoft.dotnet.xharness.cli/11.0.0-prerelease.26230.4/runtimes/any/native/adb/linux/adb -s emulator-5554 uninstall com.microsoft.maui.core.devicetests'
�[40m�[32minfo�[39m�[22m�[49m: Successfully uninstalled com.microsoft.maui.core.devicetests
XHarness exit code: 1 (TESTS_FAILED)
  Tests completed with exit code: 1

🟢 With fix — 📱 SwitchHandlerTests (DefaultSwitchReappliesLegacyOffTrackColorBeforeiOSOrMacCatalyst26, CustomColorsUseSlidingStyleOniOSOrMacCatalyst26, CustomColorsRenderOnInitialOffStateOniOSOrMacCatalyst26, DefaultSwitchUsesAutomaticStyleOniOSOrMacCatalyst26, ThumbColorClearsWhenResetOniOSOrMacCatalyst26, CustomColorsClearToAutomaticStyleOniOSOrMacCatalyst26, CustomColorsReapplyAfterMovedToWindowOniOSOrMacCatalyst26, QueuedColorReapplyDoesNotUpdateStaleNativeSwitchAfterReconnectOniOSOrMacCatalyst26, CustomColorsReapplyAfterWillEnterForegroundOniOS26, CustomColorsUpdateAfterAppThemeChangeOniOSOrMacCatalyst26): FAIL ❌ · 651s

(truncated to last 15,000 chars)

tlinX.Serialization.Core.Jvm.dll.so
  [60/128] xunit.abstractions.dll -> xunit.abstractions.dll.so
  [61/128] xunit.assert.dll -> xunit.assert.dll.so
  [62/128] xunit.core.dll -> xunit.core.dll.so
  [63/128] xunit.execution.dotnet.dll -> xunit.execution.dotnet.dll.so
  [64/128] xunit.runner.utility.netcoreapp10.dll -> xunit.runner.utility.netcoreapp10.dll.so
  [65/128] System.Collections.Concurrent.dll -> System.Collections.Concurrent.dll.so
  [66/128] System.Collections.Immutable.dll -> System.Collections.Immutable.dll.so
  [67/128] System.Collections.NonGeneric.dll -> System.Collections.NonGeneric.dll.so
  [68/128] System.Collections.Specialized.dll -> System.Collections.Specialized.dll.so
  [128/128] System.Private.CoreLib.dll -> System.Private.CoreLib.dll.so
  [69/128] System.Collections.dll -> System.Collections.dll.so
  [70/128] System.ComponentModel.Primitives.dll -> System.ComponentModel.Primitives.dll.so
  [71/128] System.ComponentModel.TypeConverter.dll -> System.ComponentModel.TypeConverter.dll.so
  [72/128] System.ComponentModel.dll -> System.ComponentModel.dll.so
  [73/128] System.Console.dll -> System.Console.dll.so
  [74/128] System.Diagnostics.Debug.dll -> System.Diagnostics.Debug.dll.so
  [75/128] System.Diagnostics.DiagnosticSource.dll -> System.Diagnostics.DiagnosticSource.dll.so
  [76/128] System.Diagnostics.Process.dll -> System.Diagnostics.Process.dll.so
  [77/128] System.Diagnostics.Tools.dll -> System.Diagnostics.Tools.dll.so
  [78/128] System.Diagnostics.TraceSource.dll -> System.Diagnostics.TraceSource.dll.so
  [79/128] System.Diagnostics.Tracing.dll -> System.Diagnostics.Tracing.dll.so
  [80/128] System.Drawing.Primitives.dll -> System.Drawing.Primitives.dll.so
  [81/128] System.Drawing.dll -> System.Drawing.dll.so
  [82/128] System.Formats.Asn1.dll -> System.Formats.Asn1.dll.so
  [83/128] System.Globalization.dll -> System.Globalization.dll.so
  [84/128] System.IO.Compression.Brotli.dll -> System.IO.Compression.Brotli.dll.so
  [85/128] System.IO.Compression.dll -> System.IO.Compression.dll.so
  [86/128] System.IO.FileSystem.dll -> System.IO.FileSystem.dll.so
  [87/128] System.IO.Pipelines.dll -> System.IO.Pipelines.dll.so
  [88/128] System.IO.dll -> System.IO.dll.so
  [89/128] System.Linq.Expressions.dll -> System.Linq.Expressions.dll.so
  [90/128] System.Linq.dll -> System.Linq.dll.so
  [91/128] System.Memory.dll -> System.Memory.dll.so
  [92/128] System.Net.Http.dll -> System.Net.Http.dll.so
  [93/128] System.Net.NameResolution.dll -> System.Net.NameResolution.dll.so
  [94/128] System.Net.Primitives.dll -> System.Net.Primitives.dll.so
  [95/128] System.Net.Requests.dll -> System.Net.Requests.dll.so
  [96/128] System.Net.Sockets.dll -> System.Net.Sockets.dll.so
  [97/128] System.Numerics.Vectors.dll -> System.Numerics.Vectors.dll.so
  [98/128] System.ObjectModel.dll -> System.ObjectModel.dll.so
  [99/128] System.Private.Uri.dll -> System.Private.Uri.dll.so
  [100/128] System.Private.Xml.Linq.dll -> System.Private.Xml.Linq.dll.so
  [101/128] System.Private.Xml.dll -> System.Private.Xml.dll.so
  [102/128] System.Reflection.Extensions.dll -> System.Reflection.Extensions.dll.so
  [103/128] System.Reflection.TypeExtensions.dll -> System.Reflection.TypeExtensions.dll.so
  [104/128] System.Reflection.dll -> System.Reflection.dll.so
  [105/128] System.Runtime.Extensions.dll -> System.Runtime.Extensions.dll.so
  [106/128] System.Runtime.InteropServices.RuntimeInformation.dll -> System.Runtime.InteropServices.RuntimeInformation.dll.so
  [107/128] System.Runtime.InteropServices.dll -> System.Runtime.InteropServices.dll.so
  [108/128] System.Runtime.Loader.dll -> System.Runtime.Loader.dll.so
  [109/128] System.Runtime.Numerics.dll -> System.Runtime.Numerics.dll.so
  [110/128] System.Runtime.dll -> System.Runtime.dll.so
  [111/128] System.Security.Cryptography.dll -> System.Security.Cryptography.dll.so
  [112/128] System.Text.Encoding.dll -> System.Text.Encoding.dll.so
  [113/128] System.Text.Encodings.Web.dll -> System.Text.Encodings.Web.dll.so
  [114/128] System.Text.Json.dll -> System.Text.Json.dll.so
  [115/128] System.Text.RegularExpressions.dll -> System.Text.RegularExpressions.dll.so
  [116/128] System.Threading.Tasks.dll -> System.Threading.Tasks.dll.so
  [117/128] System.Threading.Thread.dll -> System.Threading.Thread.dll.so
  [118/128] System.Threading.ThreadPool.dll -> System.Threading.ThreadPool.dll.so
  [119/128] System.Threading.dll -> System.Threading.dll.so
  [120/128] System.Xml.Linq.dll -> System.Xml.Linq.dll.so
  [121/128] System.Xml.ReaderWriter.dll -> System.Xml.ReaderWriter.dll.so
  [122/128] System.Xml.XDocument.dll -> System.Xml.XDocument.dll.so
  [123/128] System.dll -> System.dll.so
  [124/128] netstandard.dll -> netstandard.dll.so
  [125/128] Java.Interop.dll -> Java.Interop.dll.so
  [126/128] Mono.Android.Runtime.dll -> Mono.Android.Runtime.dll.so
  [127/128] Mono.Android.dll -> Mono.Android.dll.so
  [128/128] System.Private.CoreLib.dll -> System.Private.CoreLib.dll.so

Build succeeded.
    0 Warning(s)
    0 Error(s)

Time Elapsed 00:05:04.92
[11.0.0-prerelease.26230.4+92962e5c46ac08a66ded4c5696209cc60f1a232f] XHarness command issued: android test --app /home/vsts/work/1/s/artifacts/bin/Core.DeviceTests/Release/net10.0-android/com.microsoft.maui.core.devicetests-Signed.apk --package-name com.microsoft.maui.core.devicetests --device-id emulator-5554 -o artifacts/log --timeout 01:00:00 -v --arg TestFilter=Category=Switch
�[40m�[37mdbug�[39m�[22m�[49m: ADBRunner using ADB.exe supplied from /home/vsts/.nuget/packages/microsoft.dotnet.xharness.cli/11.0.0-prerelease.26230.4/tools/net10.0/any/../../../runtimes/any/native/adb/linux/adb
�[40m�[37mdbug�[39m�[22m�[49m: Full resolved path:'/home/vsts/.nuget/packages/microsoft.dotnet.xharness.cli/11.0.0-prerelease.26230.4/runtimes/any/native/adb/linux/adb'
�[40m�[32minfo�[39m�[22m�[49m: Will attempt to find device supporting architectures: 'arm64-v8a', 'x86_64'
�[40m�[37mdbug�[39m�[22m�[49m: Executing command: '/home/vsts/.nuget/packages/microsoft.dotnet.xharness.cli/11.0.0-prerelease.26230.4/runtimes/any/native/adb/linux/adb start-server'
�[40m�[37mdbug�[39m�[22m�[49m: 
�[40m�[32minfo�[39m�[22m�[49m: Finding attached devices/emulators...
�[40m�[37mdbug�[39m�[22m�[49m: Executing command: '/home/vsts/.nuget/packages/microsoft.dotnet.xharness.cli/11.0.0-prerelease.26230.4/runtimes/any/native/adb/linux/adb devices -l'
�[40m�[37mdbug�[39m�[22m�[49m: Found 1 possible devices
�[40m�[37mdbug�[39m�[22m�[49m: Evaluating output line for device serial: emulator-5554          device product:sdk_gphone_x86_64 model:sdk_gphone_x86_64 device:generic_x86_64_arm64 transport_id:2
�[40m�[37mdbug�[39m�[22m�[49m: Executing command: '/home/vsts/.nuget/packages/microsoft.dotnet.xharness.cli/11.0.0-prerelease.26230.4/runtimes/any/native/adb/linux/adb -s emulator-5554 shell getprop ro.product.cpu.abilist'
�[40m�[37mdbug�[39m�[22m�[49m: Found 1 possible devices. Using 'emulator-5554'
�[40m�[32minfo�[39m�[22m�[49m: Active Android device set to serial 'emulator-5554'
�[40m�[37mdbug�[39m�[22m�[49m: Executing command: '/home/vsts/.nuget/packages/microsoft.dotnet.xharness.cli/11.0.0-prerelease.26230.4/runtimes/any/native/adb/linux/adb -s emulator-5554 -s emulator-5554 shell getprop ro.product.cpu.abi'
�[40m�[37mdbug�[39m�[22m�[49m: Executing command: '/home/vsts/.nuget/packages/microsoft.dotnet.xharness.cli/11.0.0-prerelease.26230.4/runtimes/any/native/adb/linux/adb -s emulator-5554 -s emulator-5554 shell getprop ro.build.version.sdk'
�[40m�[32minfo�[39m�[22m�[49m: Waiting for device to be available (max 5 minutes)
�[40m�[37mdbug�[39m�[22m�[49m: Executing command: '/home/vsts/.nuget/packages/microsoft.dotnet.xharness.cli/11.0.0-prerelease.26230.4/runtimes/any/native/adb/linux/adb -s emulator-5554 wait-for-device'
�[40m�[37mdbug�[39m�[22m�[49m: Executing command: '/home/vsts/.nuget/packages/microsoft.dotnet.xharness.cli/11.0.0-prerelease.26230.4/runtimes/any/native/adb/linux/adb -s emulator-5554 -s emulator-5554 shell getprop sys.boot_completed'
�[40m�[37mdbug�[39m�[22m�[49m: sys.boot_completed = '1'
�[40m�[37mdbug�[39m�[22m�[49m: Waited 0 seconds for device boot completion
�[40m�[37mdbug�[39m�[22m�[49m: Working with emulator-5554 (API 30)
�[40m�[37mdbug�[39m�[22m�[49m: Check current adb install and/or package verification settings
�[40m�[37mdbug�[39m�[22m�[49m: Executing command: '/home/vsts/.nuget/packages/microsoft.dotnet.xharness.cli/11.0.0-prerelease.26230.4/runtimes/any/native/adb/linux/adb -s emulator-5554 shell settings get global verifier_verify_adb_installs'
�[40m�[37mdbug�[39m�[22m�[49m: verifier_verify_adb_installs = 0
�[40m�[37mdbug�[39m�[22m�[49m: Executing command: '/home/vsts/.nuget/packages/microsoft.dotnet.xharness.cli/11.0.0-prerelease.26230.4/runtimes/any/native/adb/linux/adb -s emulator-5554 shell settings get global package_verifier_enable'
�[40m�[37mdbug�[39m�[22m�[49m: package_verifier_enable = 
�[40m�[1m�[33mwarn�[39m�[22m�[49m: Installing debug apks on a device might be rejected with INSTALL_FAILED_VERIFICATION_FAILURE. Make sure to set 'package_verifier_enable' to '0'
�[40m�[32minfo�[39m�[22m�[49m: Attempting to remove apk 'com.microsoft.maui.core.devicetests'..
�[40m�[37mdbug�[39m�[22m�[49m: Executing command: '/home/vsts/.nuget/packages/microsoft.dotnet.xharness.cli/11.0.0-prerelease.26230.4/runtimes/any/native/adb/linux/adb -s emulator-5554 uninstall com.microsoft.maui.core.devicetests'
�[40m�[32minfo�[39m�[22m�[49m: APK 'com.microsoft.maui.core.devicetests' was not on device
�[40m�[32minfo�[39m�[22m�[49m: Attempting to install /home/vsts/work/1/s/artifacts/bin/Core.DeviceTests/Release/net10.0-android/com.microsoft.maui.core.devicetests-Signed.apk
�[40m�[37mdbug�[39m�[22m�[49m: Executing command: '/home/vsts/.nuget/packages/microsoft.dotnet.xharness.cli/11.0.0-prerelease.26230.4/runtimes/any/native/adb/linux/adb -s emulator-5554 install /home/vsts/work/1/s/artifacts/bin/Core.DeviceTests/Release/net10.0-android/com.microsoft.maui.core.devicetests-Signed.apk'
�[40m�[32minfo�[39m�[22m�[49m: Successfully installed /home/vsts/work/1/s/artifacts/bin/Core.DeviceTests/Release/net10.0-android/com.microsoft.maui.core.devicetests-Signed.apk
�[40m�[32minfo�[39m�[22m�[49m: Killing all running processes for 'com.microsoft.maui.core.devicetests': 
�[40m�[37mdbug�[39m�[22m�[49m: Executing command: '/home/vsts/.nuget/packages/microsoft.dotnet.xharness.cli/11.0.0-prerelease.26230.4/runtimes/any/native/adb/linux/adb -s emulator-5554 shell am kill --user all com.microsoft.maui.core.devicetests'
�[40m�[37mdbug�[39m�[22m�[49m: Success!
      
�[40m�[37mdbug�[39m�[22m�[49m: Executing command: '/home/vsts/.nuget/packages/microsoft.dotnet.xharness.cli/11.0.0-prerelease.26230.4/runtimes/any/native/adb/linux/adb -s emulator-5554 logcat -b all -c'
�[40m�[37mdbug�[39m�[22m�[49m: Executing command: '/home/vsts/.nuget/packages/microsoft.dotnet.xharness.cli/11.0.0-prerelease.26230.4/runtimes/any/native/adb/linux/adb -s emulator-5554 logcat -P "'\"""\"""'"'
�[40m�[1m�[33mwarn�[39m�[22m�[49m: Unable to disable chatty. Logcat may hide what it finds to be repeating entries.
�[40m�[32minfo�[39m�[22m�[49m: Starting default instrumentation class on com.microsoft.maui.core.devicetests (exit code 0 == success)
�[40m�[37mdbug�[39m�[22m�[49m: Executing command: '/home/vsts/.nuget/packages/microsoft.dotnet.xharness.cli/11.0.0-prerelease.26230.4/runtimes/any/native/adb/linux/adb -s emulator-5554 shell am instrument -e TestFilter Category=Switch -w com.microsoft.maui.core.devicetests'
�[40m�[32minfo�[39m�[22m�[49m: Running instrumentation class {default} took 12.8768825 seconds
�[40m�[37mdbug�[39m�[22m�[49m: Exit code: 0
      Std out:
      INSTRUMENTATION_RESULT: return-code=1
      INSTRUMENTATION_RESULT: test-execution-summary=Tests run: 70 Passed: 66 Inconclusive: 0 Failed: 1 Ignored: 3
      INSTRUMENTATION_RESULT: test-results-path=/storage/emulated/0/Download/com.microsoft.maui.core.devicetests/61d3f4e081b24c3289da959ecbc258d1/testResults.xml
      INSTRUMENTATION_CODE: -1
      
      
      
�[40m�[32minfo�[39m�[22m�[49m: Found XML result file: '/storage/emulated/0/Download/com.microsoft.maui.core.devicetests/61d3f4e081b24c3289da959ecbc258d1/testResults.xml'(key: test-results-path)
�[40m�[32minfo�[39m�[22m�[49m: Attempting to pull contents of /storage/emulated/0/Download/com.microsoft.maui.core.devicetests/61d3f4e081b24c3289da959ecbc258d1/testResults.xml to /home/vsts/work/1/s/artifacts/log
�[40m�[37mdbug�[39m�[22m�[49m: Executing command: '/home/vsts/.nuget/packages/microsoft.dotnet.xharness.cli/11.0.0-prerelease.26230.4/runtimes/any/native/adb/linux/adb -s emulator-5554 pull /storage/emulated/0/Download/com.microsoft.maui.core.devicetests/61d3f4e081b24c3289da959ecbc258d1/testResults.xml /tmp/dl1gbbca.w1r'
�[40m�[1m�[33mwarn�[39m�[22m�[49m: Skipping file copy as /home/vsts/work/1/s/artifacts/log/testResults.xml already exists
�[40m�[37mdbug�[39m�[22m�[49m: Copied 0 files to /home/vsts/work/1/s/artifacts/log
�[40m�[32minfo�[39m�[22m�[49m: Test execution summary:
      Tests run: 70 Passed: 66 Inconclusive: 0 Failed: 1 Ignored: 3
�[40m�[32minfo�[39m�[22m�[49m: Instrumentation finished normally with exit code 1
�[40m�[37mdbug�[39m�[22m�[49m: Executing command: '/home/vsts/.nuget/packages/microsoft.dotnet.xharness.cli/11.0.0-prerelease.26230.4/runtimes/any/native/adb/linux/adb -s emulator-5554 logcat -d '
�[40m�[32minfo�[39m�[22m�[49m: Wrote full ADB log (375 lines) to /home/vsts/work/1/s/artifacts/log/adb-logcat-com.microsoft.maui.core.devicetests-default.log
�[40m�[32minfo�[39m�[22m�[49m: ADB log contained no DOTNET-tagged entries (see full log file for details)
�[41m�[30mfail�[39m�[22m�[49m: Non-success instrumentation exit code: 1, expected: 0
�[40m�[32minfo�[39m�[22m�[49m: <<XHARNESS_RESULT_START>>
      {
        "version": 1,
        "machineName": "runnervmm79r7",
        "exitCode": 1,
        "exitCodeName": "TESTS_FAILED",
        "platform": "android",
        "instrumentationExitCode": 1,
        "device": "emulator-5554",
        "deviceOsVersion": "API 30",
        "architecture": "x86_64",
        "files": [
          {
            "name": "testResults.xml",
            "type": "test-results"
          },
          {
            "name": "adb-logcat-com.microsoft.maui.core.devicetests-default.log",
            "type": "logcat"
          }
        ]
      }
      <<XHARNESS_RESULT_END>>
�[40m�[32minfo�[39m�[22m�[49m: Attempting to remove apk 'com.microsoft.maui.core.devicetests'..
�[40m�[37mdbug�[39m�[22m�[49m: Executing command: '/home/vsts/.nuget/packages/microsoft.dotnet.xharness.cli/11.0.0-prerelease.26230.4/runtimes/any/native/adb/linux/adb -s emulator-5554 uninstall com.microsoft.maui.core.devicetests'
�[41m�[30mfail�[39m�[22m�[49m: Waiting for command timed out: execution may be compromised
�[41m�[30mfail�[39m�[22m�[49m: Error: Exit code: -2
      Std out:
      
      
      
XHarness exit code: 1 (TESTS_FAILED)
  Tests completed with exit code: 1

⚠️ Failure Details

  • SwitchHandlerTests (DefaultSwitchReappliesLegacyOffTrackColorBeforeiOSOrMacCatalyst26, CustomColorsUseSlidingStyleOniOSOrMacCatalyst26, CustomColorsRenderOnInitialOffStateOniOSOrMacCatalyst26, DefaultSwitchUsesAutomaticStyleOniOSOrMacCatalyst26, ThumbColorClearsWhenResetOniOSOrMacCatalyst26, CustomColorsClearToAutomaticStyleOniOSOrMacCatalyst26, CustomColorsReapplyAfterMovedToWindowOniOSOrMacCatalyst26, QueuedColorReapplyDoesNotUpdateStaleNativeSwitchAfterReconnectOniOSOrMacCatalyst26, CustomColorsReapplyAfterWillEnterForegroundOniOS26, CustomColorsUpdateAfterAppThemeChangeOniOSOrMacCatalyst26) FAILED with fix (should pass)
📁 Fix files reverted (3 files)
  • eng/pipelines/ci-copilot.yml
  • src/Core/src/Handlers/Switch/SwitchHandler.iOS.cs
  • src/Core/src/Platform/iOS/SwitchExtensions.cs

New files (not reverted):

  • src/Core/src/Platform/iOS/MauiSwitch.cs

UI Tests — ViewBaseTests

Detected UI test categories: ViewBaseTests

Deep UI tests — 118 passed, 0 failed across 1 category on platform-pool agent (replaces in-process counts above).

🧪 UI Test Execution Results (deep, platform pool)

Category Tests Snapshot diffs
ViewBaseTests 118/119 ✓
📎 Download drop-deep-uitests artifact (TRX + snapshot diffs)

Pre-Flight — Context & Validation

Issue: #35257 - Switch custom colors are ignored on iOS 26
PR: #35385 - [iOS] Fix Switch custom colors on iOS 26
Platforms Affected: iOS, MacCatalyst. Requested try-fix test platform: android.
Files Changed: 3 implementation, 10 test/snapshot/host-app

Key Findings

  • PR changes are iOS/MacCatalyst-specific: SwitchHandler.iOS.cs, new MauiSwitch.cs, and SwitchExtensions.cs implement style switching and color reapply for iOS/MacCatalyst 26+.
  • Current PR fix opts customized switches into UISwitchStyle.Sliding, preserves Automatic for unstyled switches, re-applies custom colors after UIKit lifecycle/style resets, and clears native thumb tint when ThumbColor resets to null.
  • The supplied gate result failed on android, but the implementation files are iOS-only; Android testing cannot directly validate the iOS 26 regression fix.
  • GitHub CLI auth was unavailable, so PR/issue/review metadata was gathered from public GitHub API and local diff instead of gh.

Code Review Summary

Verdict: LGTM
Confidence: medium
Errors: 0 | Warnings: 2 | Suggestions: 3

Key code review findings:

  • ⚠️ src/Core/src/Platform/iOS/MauiSwitch.cs:108TryReapplyColors calls low-level apply methods directly and relies on PreferredStyle already being Sliding.
  • ⚠️ src/Core/src/Handlers/Switch/SwitchHandler.iOS.cs:161 — UIKit-triggered reapply calls platform extension methods directly, bypassing mapper customizations.
  • 💡 src/Core/src/Platform/iOS/MauiSwitch.cs:62 — trait-change reapply is redundant with the proxy's user-interface-style registration.
  • 💡 src/Core/src/Platform/iOS/SwitchExtensions.cs:140 — non-MauiSwitch fallback in style reapply is unreachable through SwitchHandler.
  • 💡 src/Core/tests/DeviceTests/Handlers/Switch/SwitchHandlerTests.iOS.cs:107 — test helper uses deprecated UIGraphics.BeginImageContextWithOptions.

Fix Candidates

# Source Approach Test Result Files Changed Notes
PR PR #35385 Use MauiSwitch plus iOS/MacCatalyst 26 style switching and deferred color reapply. ❌ FAILED (Gate: Android, unrelated to iOS-only implementation) SwitchHandler.iOS.cs, MauiSwitch.cs, SwitchExtensions.cs Original PR fix.

Code Review — Deep Analysis

Code Review — PR #35385

Independent Assessment

What this changes: Creates a MauiSwitch : UISwitch subclass that self-manages a color-reapply lifecycle (overriding MovedToWindow, LayoutSubviews, TraitCollectionDidChange). Overhauling SwitchExtensions.UpdateTrackColor/UpdateThumbColor to switch UISwitch.PreferredStyle between Sliding (custom colors, iOS/Mac 26+) and Automatic (default). SwitchProxy.Disconnect now properly nulls weak references. MauiProgram.cs widens #if MACCATALYST to #if IOS || MACCATALYST for test runner argument reading.

Inferred motivation: iOS/macOS 26 shipped a "Liquid Glass" UISwitch style (UISwitchStyle.Automatic) that resets custom ThumbTintColor, OnTintColor, and track subview BackgroundColor at various UIKit lifecycle points — initial layout, MovedToWindow, theme changes, and foreground re-entry. The old code had only a partial fix (re-apply after WillEnterForeground with a Task.Delay(10) guard). This PR extends that pattern into a systematic, debounced reapply engine inside MauiSwitch.


Reconciliation with PR Narrative

Author claims: The PR title/commit is "PR #35385 squashed for review" (CI commit), so the actual PR description was not directly accessible. Based on code context, the fix targets the iOS/Mac 26 Switch color regression also addressed in the predecessor PR #33953 (bcd5905edb). This PR is a broader rework of that fix.

Agreement: The code does what the inferred intent describes. The architecture is sound and follows MAUI's MauiButton/MauiLabel subclass pattern.


Findings

⚠️ Warning — TryReapplyColors bypasses UpdatePreferredStyle

File: src/Core/src/Platform/iOS/MauiSwitch.cs, lines 107–110

TryReapplyColors calls this.ApplyTrackColor(virtualView) / this.ApplyThumbColor(virtualView) directly — these are the low-level color setters that skip UpdatePreferredStyle. On iOS/Mac 26+, colors only render correctly in UISwitchStyle.Sliding. The initial mapper call sets PreferredStyle = Sliding synchronously, and UIKit does not appear to reset it. However, if that assumption breaks (e.g., a future OS version, or a view hierarchy teardown-reconnect), this reapply path would apply colors in Automatic style where they are silently ignored.

In practice this is guarded by IsReadyForColorReapply() (view must be in window with non-zero bounds), and the mapper path runs before MovedToWindow fires — so PreferredStyle should be Sliding by the time TryReapplyColors can succeed. Worth a code comment to document the assumption.

⚠️ Warning — UIKit reapply bypasses mapper customizations

File: src/Core/src/Handlers/Switch/SwitchHandler.iOS.cs, line 161

UpdateThumbAndTrackColor calls platformView.UpdateTrackColor(view) / platformView.UpdateThumbColor(view) directly rather than routing through handler.UpdateValue(nameof(ISwitch.TrackColor)). This bypasses AppendToMapping/PrependToMapping customizations. If an app developer registered a mapper extension on TrackColor or ThumbColor, that customization would be dropped on every UIKit-triggered reapply (theme change, foreground re-entry). This is a known MAUI anti-pattern (see handler-patterns guidelines). Should be documented with a comment explaining why the mapper bypass is intentional here.

💡 Suggestion — Redundant trait-change reapply path

File: src/Core/src/Platform/iOS/MauiSwitch.cs, line 62

MauiSwitch.TraitCollectionDidChange fires for ALL trait types, while SwitchProxy._traitChangeRegistration is scoped to UITraitUserInterfaceStyle. On iOS 26+ theme changes, both paths fire and both trigger async color reapplication. Idempotent but redundant. Consider removing TraitCollectionDidChange from MauiSwitch since the proxy registration already handles the main scenario, or document why the broader hook is needed.

💡 Suggestion — ReapplyColorsAfterStyleUpdate unreachable else branch

File: src/Core/src/Platform/iOS/SwitchExtensions.cs, lines 136–144

The else branch for non-MauiSwitch instances is unreachable through SwitchHandler, which always creates MauiSwitch. May be intentional for non-handler consumers of UpdateTrackColor/UpdateThumbColor. If not, simplify to the MauiSwitch-only path.

💡 Suggestion — Deprecated API in test helper

File: src/Core/tests/DeviceTests/Handlers/Switch/SwitchHandlerTests.iOS.cs, line ~107

CaptureRenderedSwitch uses UIGraphics.BeginImageContextWithOptions which is deprecated in iOS 16+. Recommended replacement: UIGraphicsImageRenderer. Minor in test code.


Devil's Advocate

Challenge: Is the gate failure caused by this PR?
The gate ran on "Platform: ANDROID" but all changed files are iOS-only (.iOS.cs, Platform/iOS/). No Android code was changed. The failing test ThumbColorUpdatesCorrectly exists in the shared Switch category on Android and has no relationship to the iOS changes. This is most likely a pre-existing Android test flake or a gate configuration issue. The gate result should be treated as a gate infrastructure failure, not a code regression.

Challenge: Is _isReapplyingColors guard necessary?
TryReapplyColors always runs on the main thread via DispatchAsync. Since UIKit is single-threaded, re-entrancy cannot occur. The guard is harmless defensive programming.

Challenge: Could _needsColorReapply create a perpetual reapply loop?
UpdateThumbAndTrackColor → pipeline → SetNeedsColorReapply()TryReapplyColors_needsColorReapply = false. Each cycle terminates. No loop.


Verdict: LGTM

Confidence: medium (iOS 26 platform behavior cannot be verified in this review environment)

Summary: The implementation is architecturally sound, follows the MauiSwitch subclass pattern used elsewhere in MAUI, and adds comprehensive device test coverage for all the new iOS 26 scenarios. The two warnings (style bypass in TryReapplyColors, mapper bypass in UpdateThumbAndTrackColor) are implementation choices that work correctly today under their stated assumptions, and are worth documenting with inline comments. The gate failure on Android is unrelated to this PR. Ready for human reviewer sign-off.


Fix — Analysis & Comparison

Fix Candidates

# Source Approach Test Result Files Changed Notes
1 maui-expert-reviewer Remove MauiSwitch; route delayed reapply through handler.UpdateValue so mapper customizations and style updates are honored. ❌ Failed / environment-blocked 3 implementation files Android device-test command built assemblies but VSTest aborted with libhostpolicy.so/runtimeconfig error before tests ran.
2 maui-expert-reviewer Keep MauiSwitch, but make TryReapplyColors mapper-aware and replace Task.Delay(10) proxy work with nested main-queue dispatches that re-arm MauiSwitch. ⚠️ Blocked 2 implementation files Android execution blocked by same test-host issue; best-effort iOS build blocked by missing Microsoft.iOS.Sdk.net10.0_26.0 workload.
3 maui-expert-reviewer Eager style-only approach: create plain UISwitch, force UISwitchStyle.Sliding on iOS/MacCatalyst 26+, delete MauiSwitch and most reapply machinery. ⚠️ Blocked / not selected 3 implementation files Simpler but likely regresses unstyled iOS 26 switches by forcing classic style instead of preserving native Automatic.
PR PR #35385 Use MauiSwitch plus conditional iOS/MacCatalyst 26 style switching and deferred color reapply; preserve Automatic for unstyled switches. ❌ FAILED (Gate: Android) 3 implementation files + tests/snapshots Original PR fix. Android gate does not directly exercise iOS-only implementation.

Cross-Pollination

Model Round New Ideas? Details
maui-expert-reviewer 1 Yes Candidate 1: no-subclass mapper-routed reapply.
maui-expert-reviewer 2 Yes Candidate 2: keep subclass but route final reapply through mapper and remove wall-clock delay.
maui-expert-reviewer 3 Yes Candidate 3: eager style-only fix with no lifecycle state machine.
orchestrator 4 No Meaningfully different families are exhausted: no-subclass mapper reapply, subclass mapper reapply, and style-only. Further variants would be timing tweaks rather than independent approaches.

Exhausted: Yes
Selected Fix: PR #35385 — none of the alternatives passed all tests or was demonstrably better. Candidate 2 is the most promising improvement idea because it addresses mapper-bypass concerns while preserving the PR's lifecycle robustness, but it remains unvalidated in this environment. Candidate 3 is simpler but likely worse because it changes default unstyled iOS 26 switch appearance.

Failure/Blocker Summary

  • Requested test platform was Android, but the PR and all candidates modify iOS/MacCatalyst-only files.
  • The Android dotnet test command used for try-fix-1 did not execute tests; VSTest aborted with a libhostpolicy.so/runtimeconfig host error.
  • Direct iOS build validation is unavailable on this Linux host because Microsoft.iOS.Sdk.net10.0_26.0 workload packs are missing.
  • Because no candidate passed tests, no alternative can be recommended over the PR fix.

Report — Final Recommendation

Comparative Report — PR #35385

Candidate ranking

Rank Candidate Test/regression status Assessment
1 pr-plus-reviewer ⚠️ Blocked: iOS build unavailable on Linux due missing Microsoft.iOS.Sdk.net10.0_26.0; Android gate not re-run by instruction Best candidate. Keeps the PR's iOS/MacCatalyst 26 lifecycle/style strategy and applies the expert reviewer's actionable mapper-aware reapply fix.
2 try-fix-2 ⚠️ Blocked: same Android test-host and missing iOS workload blockers recorded in STEP 5a Technically similar to pr-plus-reviewer and addresses the mapper-bypass issue, but as an independent try-fix it remained unvalidated and is superseded by applying the same improvement to the PR-reviewed candidate.
3 pr ❌ Gate failed on Android; failure is not directly meaningful for iOS-only implementation The raw PR preserves native defaults and has broad lifecycle coverage, but the expert reviewer found a major mapper-bypass issue in MauiSwitch.TryReapplyColors.
4 try-fix-1 ❌ Failed/environment-blocked: Android VSTest aborted before tests ran with libhostpolicy.so/runtimeconfig error Routes proxy reapply through the mapper but removes MauiSwitch, losing the PR's layout/window reapply safety net for iOS 26 UIKit resets.
5 try-fix-3 ⚠️ Blocked/not selected Simplest style-only approach, but likely regresses product behavior by forcing unstyled iOS/MacCatalyst 26 switches to classic Sliding instead of preserving native Automatic, and lacks lifecycle reapply coverage.

No candidate has a clean regression-test pass in this environment. The only explicit gate failure is Android-based and does not directly exercise the iOS/MacCatalyst-only fix, but candidates with failed regression execution are still ranked below the stronger blocked/code-reviewed candidates where appropriate.

Detailed comparison

pr

The raw PR introduces MauiSwitch, switches customized iOS/MacCatalyst 26 switches to UISwitchStyle.Sliding, keeps unstyled switches on Automatic, reapplies custom colors after UIKit lifecycle/style resets, and clears native tint state when custom colors are removed. This is the most complete product-oriented approach among the original candidates, but the expert reviewer identified a concrete mapper customization bug: lifecycle reapply calls low-level ApplyTrackColor/ApplyThumbColor directly and can overwrite app/library mapper extensions after trait/window/layout events.

pr-plus-reviewer

This candidate keeps the raw PR's architecture and applies the expert reviewer feedback by making the final lifecycle reapply mapper-aware. It avoids the raw PR's customization bypass while preserving the important safeguards that try-fix-1 and try-fix-3 weaken or remove. Its validation is blocked by the same missing iOS workload pack as other iOS-specific candidates, but by code review it is the best balance of correctness, compatibility, and risk.

try-fix-1

This candidate removes MauiSwitch and routes delayed callbacks through handler.UpdateValue. The mapper behavior is better than the raw PR, but removing the subclass also removes the broader layout/window/trait reapply state machine that appears central to the iOS 26 UIKit reset workaround. Its Android test command failed before executing tests, so there is no empirical evidence that the simplification is safe.

try-fix-2

This candidate is the strongest STEP 5a alternative: it keeps MauiSwitch, routes final reapply through the mapper, and removes wall-clock delay from proxy callbacks in favor of queued main-thread reapply. It is effectively the basis for pr-plus-reviewer. It remains blocked by test/build environment limitations, so it is ranked below the PR-applied reviewer candidate only because pr-plus-reviewer is the explicitly reviewed PR candidate requested by Phase 1.

try-fix-3

This candidate eagerly forces UISwitchStyle.Sliding for all iOS/MacCatalyst 26 switches and deletes the reapply machinery. That simplicity likely changes default unstyled switch appearance and leaves fewer protections against later UIKit resets. It should not win without iOS 26 visual/device evidence.

Winner

Winner: pr-plus-reviewer

pr-plus-reviewer wins because it preserves the PR's intended iOS 26 behavior and lifecycle resilience while fixing the expert reviewer's actionable mapper-customization issue. No candidate has passing regression evidence in this environment, and none of the try-fix alternatives is demonstrably safer or more complete.


Future Action — review latest findings

No alternative fix was selected for this run. Review the session findings and CI results before merging.

@kubaflo
Copy link
Copy Markdown
Contributor

kubaflo commented Jun 3, 2026

/azp run

@azure-pipelines
Copy link
Copy Markdown

Azure Pipelines successfully started running 3 pipeline(s).

Copy link
Copy Markdown
Contributor

@kubaflo kubaflo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it looks like regression - I compared switches and they look incorrectly now

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

community ✨ Community Contribution s/agent-fix-pr-picked AI could not beat the PR fix - PR is the best among all candidates s/agent-reviewed PR was reviewed by AI agent workflow (full 4-phase review)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

iOS 26 Switch default color for Off and On is incorrect + Off Color is not applied at start + Thumb Colors is not applied